meilisearch_sdk/indexes.rs
1use crate::{
2 client::Client,
3 documents::{DocumentDeletionQuery, DocumentQuery, DocumentsQuery, DocumentsResults},
4 errors::{Error, MeilisearchCommunicationError, MeilisearchError, MEILISEARCH_VERSION_HINT},
5 request::*,
6 search::*,
7 similar::*,
8 task_info::TaskInfo,
9 tasks::*,
10 DefaultHttpClient,
11};
12use serde::{de::DeserializeOwned, Deserialize, Serialize};
13use std::{collections::HashMap, fmt::Display, time::Duration};
14use time::OffsetDateTime;
15
16/// A Meilisearch [index](https://www.meilisearch.com/docs/learn/core_concepts/indexes).
17///
18/// # Example
19///
20/// You can create an index remotely and, if that succeed, make an `Index` out of it.
21/// See the [`Client::create_index`] method.
22/// ```
23/// # use meilisearch_sdk::{client::*, indexes::*};
24/// #
25/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
26/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
27/// #
28/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
29/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
30///
31/// // get the index called movies or create it if it does not exist
32/// let movies = client
33/// .create_index("index", None)
34/// .await
35/// .unwrap()
36/// // We wait for the task to execute until completion
37/// .wait_for_completion(&client, None, None)
38/// .await
39/// .unwrap()
40/// // Once the task finished, we try to create an `Index` out of it
41/// .try_make_index(&client)
42/// .unwrap();
43///
44/// assert_eq!(movies.as_ref(), "index");
45/// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
46/// # });
47/// ```
48///
49/// Or, if you know the index already exist remotely you can create an [Index] with its builder.
50/// ```
51/// # use meilisearch_sdk::{client::*, indexes::*};
52/// #
53/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
54/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
55/// #
56/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
57/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
58///
59/// // Meilisearch would be able to create the index if it does not exist during:
60/// // - the documents addition (add and update routes)
61/// // - the settings update
62/// let movies = Index::new("movies", client);
63///
64/// assert_eq!(movies.uid, "movies");
65/// # });
66/// ```
67#[derive(Debug, Serialize, Clone)]
68#[serde(rename_all = "camelCase")]
69pub struct Index<Http: HttpClient = DefaultHttpClient> {
70 #[serde(skip_serializing)]
71 pub client: Client<Http>,
72 pub uid: String,
73 #[serde(with = "time::serde::rfc3339::option")]
74 pub updated_at: Option<OffsetDateTime>,
75 #[serde(with = "time::serde::rfc3339::option")]
76 pub created_at: Option<OffsetDateTime>,
77 pub primary_key: Option<String>,
78}
79
80impl<Http: HttpClient> Index<Http> {
81 pub fn new(uid: impl Into<String>, client: Client<Http>) -> Index<Http> {
82 Index {
83 uid: uid.into(),
84 client,
85 primary_key: None,
86 created_at: None,
87 updated_at: None,
88 }
89 }
90 /// Internal Function to create an [Index] from `serde_json::Value` and [Client].
91 pub(crate) fn from_value(
92 raw_index: serde_json::Value,
93 client: Client<Http>,
94 ) -> Result<Index<Http>, Error> {
95 #[derive(Deserialize, Debug)]
96 #[allow(non_snake_case)]
97 struct IndexFromSerde {
98 uid: String,
99 #[serde(with = "time::serde::rfc3339::option")]
100 updatedAt: Option<OffsetDateTime>,
101 #[serde(with = "time::serde::rfc3339::option")]
102 createdAt: Option<OffsetDateTime>,
103 primaryKey: Option<String>,
104 }
105
106 let i: IndexFromSerde = serde_json::from_value(raw_index).map_err(Error::ParseError)?;
107
108 Ok(Index {
109 uid: i.uid,
110 client,
111 created_at: i.createdAt,
112 updated_at: i.updatedAt,
113 primary_key: i.primaryKey,
114 })
115 }
116
117 /// Update an [Index].
118 ///
119 /// # Example
120 ///
121 /// ```
122 /// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::*};
123 /// #
124 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
125 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
126 /// #
127 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
128 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
129 /// # let mut index = client
130 /// # .create_index("index_update", None)
131 /// # .await
132 /// # .unwrap()
133 /// # .wait_for_completion(&client, None, None)
134 /// # .await
135 /// # .unwrap()
136 /// # // Once the task finished, we try to create an `Index` out of it
137 /// # .try_make_index(&client)
138 /// # .unwrap();
139 /// #
140 /// index.primary_key = Some("special_id".to_string());
141 /// let task = index.update()
142 /// .await
143 /// .unwrap()
144 /// .wait_for_completion(&client, None, None)
145 /// .await
146 /// .unwrap();
147 ///
148 /// let index = client.get_index("index_update").await.unwrap();
149 ///
150 /// assert_eq!(index.primary_key, Some("special_id".to_string()));
151 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
152 /// # });
153 /// ```
154 pub async fn update(&self) -> Result<TaskInfo, Error> {
155 let mut index_update = IndexUpdater::new(self, &self.client);
156
157 if let Some(ref primary_key) = self.primary_key {
158 index_update.with_primary_key(primary_key);
159 }
160
161 index_update.execute().await
162 }
163
164 /// Delete the index.
165 ///
166 /// # Example
167 ///
168 /// ```
169 /// # use meilisearch_sdk::{client::*, indexes::*};
170 /// #
171 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
172 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
173 /// #
174 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
175 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
176 /// # let index = client.create_index("delete", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
177 ///
178 /// // get the index named "movies" and delete it
179 /// let index = client.index("delete");
180 /// let task = index.delete().await.unwrap();
181 ///
182 /// client.wait_for_task(task, None, None).await.unwrap();
183 /// # });
184 /// ```
185 pub async fn delete(self) -> Result<TaskInfo, Error> {
186 self.client
187 .http_client
188 .request::<(), (), TaskInfo>(
189 &format!("{}/indexes/{}", self.client.host, self.uid),
190 Method::Delete { query: () },
191 202,
192 )
193 .await
194 }
195
196 /// Search for documents matching a specific query in the index.
197 ///
198 /// See also [`Index::search`].
199 ///
200 /// # Example
201 ///
202 /// ```
203 /// # use serde::{Serialize, Deserialize};
204 /// # use meilisearch_sdk::{client::*, indexes::*, search::*};
205 /// #
206 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
207 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
208 /// #
209 /// #[derive(Serialize, Deserialize, Debug)]
210 /// struct Movie {
211 /// name: String,
212 /// description: String,
213 /// }
214 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
215 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
216 /// let movies = client.index("execute_query");
217 ///
218 /// // add some documents
219 /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")},Movie{name:String::from("Unknown"), description:String::from("Unknown")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
220 ///
221 /// let query = SearchQuery::new(&movies).with_query("Interstellar").with_limit(5).build();
222 /// let results = movies.execute_query::<Movie>(&query).await.unwrap();
223 ///
224 /// assert!(results.hits.len() > 0);
225 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
226 /// # });
227 /// ```
228 pub async fn execute_query<T: 'static + DeserializeOwned + Send + Sync>(
229 &self,
230 body: &SearchQuery<'_, Http>,
231 ) -> Result<SearchResults<T>, Error> {
232 self.client
233 .http_client
234 .request::<(), &SearchQuery<Http>, SearchResults<T>>(
235 &format!("{}/indexes/{}/search", self.client.host, self.uid),
236 Method::Post { body, query: () },
237 200,
238 )
239 .await
240 }
241
242 /// Search for documents matching a specific query in the index.
243 ///
244 /// See also [`Index::execute_query`].
245 ///
246 /// # Example
247 ///
248 /// ```
249 /// # use serde::{Serialize, Deserialize};
250 /// # use meilisearch_sdk::{client::*, indexes::*, search::*};
251 /// #
252 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
253 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
254 /// #
255 /// #[derive(Serialize, Deserialize, Debug)]
256 /// struct Movie {
257 /// name: String,
258 /// description: String,
259 /// }
260 ///
261 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
262 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
263 /// let mut movies = client.index("search");
264 /// # // add some documents
265 /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")},Movie{name:String::from("Unknown"), description:String::from("Unknown")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
266 ///
267 /// let results = movies.search()
268 /// .with_query("Interstellar")
269 /// .with_limit(5)
270 /// .execute::<Movie>()
271 /// .await
272 /// .unwrap();
273 ///
274 /// assert!(results.hits.len() > 0);
275 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
276 /// # });
277 /// ```
278 #[must_use]
279 pub fn search(&self) -> SearchQuery<'_, Http> {
280 SearchQuery::new(self)
281 }
282
283 /// Returns the facet stats matching a specific query in the index.
284 ///
285 /// See also [`Index::facet_search`].
286 ///
287 /// # Example
288 ///
289 /// ```
290 /// # use serde::{Serialize, Deserialize};
291 /// # use meilisearch_sdk::{client::*, indexes::*, search::*};
292 /// #
293 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
294 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
295 /// #
296 /// #[derive(Serialize, Deserialize, Debug)]
297 /// struct Movie {
298 /// name: String,
299 /// genre: String,
300 /// }
301 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
302 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
303 /// let movies = client.index("execute_query2");
304 ///
305 /// // add some documents
306 /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), genre:String::from("scifi")},Movie{name:String::from("Inception"), genre:String::from("drama")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
307 /// # movies.set_filterable_attributes(["genre"]).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
308 ///
309 /// let query = FacetSearchQuery::new(&movies, "genre").with_facet_query("scifi").build();
310 /// let res = movies.execute_facet_query(&query).await.unwrap();
311 ///
312 /// assert!(res.facet_hits.len() > 0);
313 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
314 /// # });
315 /// ```
316 pub async fn execute_facet_query(
317 &self,
318 body: &FacetSearchQuery<'_, Http>,
319 ) -> Result<FacetSearchResponse, Error> {
320 self.client
321 .http_client
322 .request::<(), &FacetSearchQuery<Http>, FacetSearchResponse>(
323 &format!("{}/indexes/{}/facet-search", self.client.host, self.uid),
324 Method::Post { body, query: () },
325 200,
326 )
327 .await
328 }
329
330 pub fn facet_search<'a>(&'a self, facet_name: &'a str) -> FacetSearchQuery<'a, Http> {
331 FacetSearchQuery::new(self, facet_name)
332 }
333
334 /// Get one document using its unique id.
335 ///
336 /// Serde is needed. Add `serde = {version="1.0", features=["derive"]}` in the dependencies section of your Cargo.toml.
337 ///
338 /// # Example
339 ///
340 /// ```
341 /// # use serde::{Serialize, Deserialize};
342 /// # use meilisearch_sdk::{client::*, indexes::*};
343 /// #
344 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
345 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
346 /// #
347 /// #[derive(Serialize, Deserialize, Debug, PartialEq)]
348 /// struct Movie {
349 /// name: String,
350 /// description: String
351 /// }
352 ///
353 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
354 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
355 /// let movies = client.index("get_document");
356 /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
357 ///
358 /// // retrieve a document (you have to put the document in the index before)
359 /// let interstellar = movies.get_document::<Movie>("Interstellar").await.unwrap();
360 ///
361 /// assert_eq!(interstellar, Movie {
362 /// name: String::from("Interstellar"),
363 /// description: String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage."),
364 /// });
365 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
366 /// # });
367 /// ```
368 pub async fn get_document<T: 'static + DeserializeOwned + Send + Sync>(
369 &self,
370 document_id: &str,
371 ) -> Result<T, Error> {
372 let url = format!(
373 "{}/indexes/{}/documents/{}",
374 self.client.host, self.uid, document_id
375 );
376 self.client
377 .http_client
378 .request::<(), (), T>(&url, Method::Get { query: () }, 200)
379 .await
380 }
381
382 /// Get one document with parameters.
383 ///
384 /// # Example
385 ///
386 /// ```
387 /// # use meilisearch_sdk::{client::*, indexes::*, documents::*};
388 /// # use serde::{Deserialize, Serialize};
389 /// #
390 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
391 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
392 /// #
393 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
394 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
395 /// #[derive(Debug, Serialize, Deserialize, PartialEq)]
396 /// struct MyObject {
397 /// id: String,
398 /// kind: String,
399 /// }
400 ///
401 /// #[derive(Debug, Serialize, Deserialize, PartialEq)]
402 /// struct MyObjectReduced {
403 /// id: String,
404 /// }
405 /// # let index = client.index("document_query_execute");
406 /// # index.add_or_replace(&[MyObject{id:"1".to_string(), kind:String::from("a kind")},MyObject{id:"2".to_string(), kind:String::from("some kind")}], None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
407 ///
408 /// let mut document_query = DocumentQuery::new(&index);
409 /// document_query.with_fields(["id"]);
410 ///
411 /// let document = index.get_document_with::<MyObjectReduced>("1", &document_query).await.unwrap();
412 ///
413 /// assert_eq!(
414 /// document,
415 /// MyObjectReduced { id: "1".to_string() }
416 /// );
417 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
418 /// # });
419 pub async fn get_document_with<T: 'static + DeserializeOwned + Send + Sync>(
420 &self,
421 document_id: &str,
422 document_query: &DocumentQuery<'_, Http>,
423 ) -> Result<T, Error> {
424 let url = format!(
425 "{}/indexes/{}/documents/{}",
426 self.client.host, self.uid, document_id
427 );
428 self.client
429 .http_client
430 .request::<&DocumentQuery<Http>, (), T>(
431 &url,
432 Method::Get {
433 query: document_query,
434 },
435 200,
436 )
437 .await
438 }
439
440 /// Get documents by batch.
441 ///
442 /// # Example
443 ///
444 /// ```
445 /// # use serde::{Serialize, Deserialize};
446 /// # use meilisearch_sdk::{client::*, indexes::*};
447 /// #
448 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
449 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
450 /// #
451 /// #[derive(Serialize, Deserialize, PartialEq, Debug)]
452 /// struct Movie {
453 /// name: String,
454 /// description: String,
455 /// }
456 ///
457 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
458 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
459 /// let movie_index = client.index("get_documents");
460 /// # movie_index.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
461 ///
462 /// // retrieve movies (you have to put some movies in the index before)
463 /// let movies = movie_index.get_documents::<Movie>().await.unwrap();
464 ///
465 /// assert!(movies.results.len() > 0);
466 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
467 /// # });
468 /// ```
469 pub async fn get_documents<T: DeserializeOwned + 'static + Send + Sync>(
470 &self,
471 ) -> Result<DocumentsResults<T>, Error> {
472 let url = format!("{}/indexes/{}/documents", self.client.host, self.uid);
473 self.client
474 .http_client
475 .request::<(), (), DocumentsResults<T>>(&url, Method::Get { query: () }, 200)
476 .await
477 }
478
479 /// Get documents by batch with parameters.
480 ///
481 /// # Example
482 ///
483 /// ```
484 /// # use serde::{Serialize, Deserialize};
485 /// # use meilisearch_sdk::{client::*, indexes::*, documents::*};
486 /// #
487 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
488 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
489 /// #
490 /// #[derive(Serialize, Deserialize, PartialEq, Debug)]
491 /// struct Movie {
492 /// name: String,
493 /// description: String,
494 /// }
495 ///
496 /// #[derive(Deserialize, Debug, PartialEq)]
497 /// struct ReturnedMovie {
498 /// name: String,
499 /// }
500 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
501 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
502 ///
503 /// let movie_index = client.index("get_documents_with");
504 /// # movie_index.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
505 ///
506 /// let mut query = DocumentsQuery::new(&movie_index);
507 /// query.with_limit(1);
508 /// query.with_fields(["name"]);
509 /// // retrieve movies (you have to put some movies in the index before)
510 /// let movies = movie_index.get_documents_with::<ReturnedMovie>(&query).await.unwrap();
511 ///
512 /// assert_eq!(movies.results.len(), 1);
513 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
514 /// # });
515 /// ```
516 pub async fn get_documents_with<T: DeserializeOwned + 'static + Send + Sync>(
517 &self,
518 documents_query: &DocumentsQuery<'_, Http>,
519 ) -> Result<DocumentsResults<T>, Error> {
520 if documents_query.filter.is_some() || documents_query.ids.is_some() {
521 let url = format!("{}/indexes/{}/documents/fetch", self.client.host, self.uid);
522 return self
523 .client
524 .http_client
525 .request::<(), &DocumentsQuery<Http>, DocumentsResults<T>>(
526 &url,
527 Method::Post {
528 body: documents_query,
529 query: (),
530 },
531 200,
532 )
533 .await
534 .map_err(|err| match err {
535 Error::MeilisearchCommunication(error) => {
536 Error::MeilisearchCommunication(MeilisearchCommunicationError {
537 status_code: error.status_code,
538 url: error.url,
539 message: Some(format!("{MEILISEARCH_VERSION_HINT}.")),
540 })
541 }
542 Error::Meilisearch(error) => Error::Meilisearch(MeilisearchError {
543 error_code: error.error_code,
544 error_link: error.error_link,
545 error_type: error.error_type,
546 error_message: format!(
547 "{}\n{}.",
548 error.error_message, MEILISEARCH_VERSION_HINT
549 ),
550 }),
551 _ => err,
552 });
553 }
554
555 let url = format!("{}/indexes/{}/documents", self.client.host, self.uid);
556 self.client
557 .http_client
558 .request::<&DocumentsQuery<Http>, (), DocumentsResults<T>>(
559 &url,
560 Method::Get {
561 query: documents_query,
562 },
563 200,
564 )
565 .await
566 }
567
568 /// Add a list of documents or replace them if they already exist.
569 ///
570 /// If you send an already existing document (same id) the **whole existing document** will be overwritten by the new document.
571 /// Fields previously in the document not present in the new document are removed.
572 ///
573 /// For a partial update of the document see [`Index::add_or_update`].
574 ///
575 /// You can use the alias [`Index::add_documents`] if you prefer.
576 ///
577 /// # Example
578 ///
579 /// ```
580 /// # use serde::{Serialize, Deserialize};
581 /// # use meilisearch_sdk::{client::*, indexes::*};
582 /// # use std::thread::sleep;
583 /// # use std::time::Duration;
584 /// #
585 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
586 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
587 /// #
588 /// #[derive(Serialize, Deserialize, Debug)]
589 /// struct Movie {
590 /// name: String,
591 /// description: String,
592 /// }
593 ///
594 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
595 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
596 /// let movie_index = client.index("add_or_replace");
597 ///
598 /// let task = movie_index.add_or_replace(&[
599 /// Movie{
600 /// name: String::from("Interstellar"),
601 /// description: String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")
602 /// },
603 /// Movie{
604 /// // note that the id field can only take alphanumerics characters (and '-' and '/')
605 /// name: String::from("MrsDoubtfire"),
606 /// description: String::from("Loving but irresponsible dad Daniel Hillard, estranged from his exasperated spouse, is crushed by a court order allowing only weekly visits with his kids. When Daniel learns his ex needs a housekeeper, he gets the job -- disguised as an English nanny. Soon he becomes not only his children's best pal but the kind of parent he should have been from the start.")
607 /// },
608 /// Movie{
609 /// name: String::from("Apollo13"),
610 /// description: String::from("The true story of technical troubles that scuttle the Apollo 13 lunar mission in 1971, risking the lives of astronaut Jim Lovell and his crew, with the failed journey turning into a thrilling saga of heroism. Drifting more than 200,000 miles from Earth, the astronauts work furiously with the ground crew to avert tragedy.")
611 /// },
612 /// ], Some("name")).await.unwrap();
613 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
614 /// client.wait_for_task(task, None, None).await.unwrap();
615 ///
616 /// let movies = movie_index.get_documents::<Movie>().await.unwrap();
617 /// assert!(movies.results.len() >= 3);
618 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
619 /// # });
620 /// ```
621 pub async fn add_or_replace<T: Serialize + Send + Sync>(
622 &self,
623 documents: &[T],
624 primary_key: Option<&str>,
625 ) -> Result<TaskInfo, Error> {
626 let url = if let Some(primary_key) = primary_key {
627 format!(
628 "{}/indexes/{}/documents?primaryKey={}",
629 self.client.host, self.uid, primary_key
630 )
631 } else {
632 format!("{}/indexes/{}/documents", self.client.host, self.uid)
633 };
634 self.client
635 .http_client
636 .request::<(), &[T], TaskInfo>(
637 &url,
638 Method::Post {
639 query: (),
640 body: documents,
641 },
642 202,
643 )
644 .await
645 }
646
647 /// Add a raw and unchecked payload to meilisearch.
648 ///
649 /// This can be useful if your application is only forwarding data from other sources.
650 ///
651 /// If you send an already existing document (same id) the **whole existing document** will be overwritten by the new document.
652 /// Fields previously in the document not present in the new document are removed.
653 ///
654 /// For a partial update of the document see [`Index::add_or_update_unchecked_payload`].
655 ///
656 /// # Example
657 ///
658 /// ```
659 /// # use serde::{Serialize, Deserialize};
660 /// # use meilisearch_sdk::{client::*, indexes::*};
661 /// # use std::thread::sleep;
662 /// # use std::time::Duration;
663 /// #
664 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
665 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
666 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
667 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
668 /// let movie_index = client.index("add_or_replace_unchecked_payload");
669 ///
670 /// let task = movie_index.add_or_replace_unchecked_payload(
671 /// r#"{ "id": 1, "body": "doggo" }
672 /// { "id": 2, "body": "catto" }"#.as_bytes(),
673 /// "application/x-ndjson",
674 /// Some("id"),
675 /// ).await.unwrap();
676 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
677 /// client.wait_for_task(task, None, None).await.unwrap();
678 ///
679 /// let movies = movie_index.get_documents::<serde_json::Value>().await.unwrap();
680 /// assert_eq!(movies.results.len(), 2);
681 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
682 /// # });
683 /// ```
684 pub async fn add_or_replace_unchecked_payload<
685 T: futures_io::AsyncRead + Send + Sync + 'static,
686 >(
687 &self,
688 payload: T,
689 content_type: &str,
690 primary_key: Option<&str>,
691 ) -> Result<TaskInfo, Error> {
692 let url = if let Some(primary_key) = primary_key {
693 format!(
694 "{}/indexes/{}/documents?primaryKey={}",
695 self.client.host, self.uid, primary_key
696 )
697 } else {
698 format!("{}/indexes/{}/documents", self.client.host, self.uid)
699 };
700 self.client
701 .http_client
702 .stream_request::<(), T, TaskInfo>(
703 &url,
704 Method::Post {
705 query: (),
706 body: payload,
707 },
708 content_type,
709 202,
710 )
711 .await
712 }
713
714 /// Alias for [`Index::add_or_replace`].
715 pub async fn add_documents<T: Serialize + Send + Sync>(
716 &self,
717 documents: &[T],
718 primary_key: Option<&str>,
719 ) -> Result<TaskInfo, Error> {
720 self.add_or_replace(documents, primary_key).await
721 }
722
723 /// Add a raw ndjson payload and update them if they already exist.
724 ///
725 /// It configures the correct content type for ndjson data.
726 ///
727 /// If you send an already existing document (same id) the old document will be only partially updated according to the fields of the new document.
728 /// Thus, any fields not present in the new document are kept and remained unchanged.
729 ///
730 /// To completely overwrite a document, check out the [`Index::add_documents_ndjson`] documents method.
731 ///
732 /// # Example
733 ///
734 /// ```
735 /// # use serde::{Serialize, Deserialize};
736 /// # use meilisearch_sdk::{client::*, indexes::*};
737 /// # use std::thread::sleep;
738 /// # use std::time::Duration;
739 /// #
740 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
741 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
742 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
743 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
744 /// let movie_index = client.index("update_documents_ndjson");
745 ///
746 /// let task = movie_index.update_documents_ndjson(
747 /// r#"{ "id": 1, "body": "doggo" }
748 /// { "id": 2, "body": "catto" }"#.as_bytes(),
749 /// Some("id"),
750 /// ).await.unwrap();
751 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
752 /// client.wait_for_task(task, None, None).await.unwrap();
753 ///
754 /// let movies = movie_index.get_documents::<serde_json::Value>().await.unwrap();
755 /// assert_eq!(movies.results.len(), 2);
756 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
757 /// # });
758 /// ```
759 #[cfg(not(target_arch = "wasm32"))]
760 pub async fn update_documents_ndjson<T: futures_io::AsyncRead + Send + Sync + 'static>(
761 &self,
762 payload: T,
763 primary_key: Option<&str>,
764 ) -> Result<TaskInfo, Error> {
765 self.add_or_update_unchecked_payload(payload, "application/x-ndjson", primary_key)
766 .await
767 }
768
769 /// Add a raw ndjson payload to meilisearch.
770 ///
771 /// It configures the correct content type for ndjson data.
772 ///
773 /// If you send an already existing document (same id) the **whole existing document** will be overwritten by the new document.
774 /// Fields previously in the document not present in the new document are removed.
775 ///
776 /// For a partial update of the document see [`Index::update_documents_ndjson`].
777 ///
778 /// # Example
779 ///
780 /// ```
781 /// # use serde::{Serialize, Deserialize};
782 /// # use meilisearch_sdk::{client::*, indexes::*};
783 /// # use std::thread::sleep;
784 /// # use std::time::Duration;
785 /// #
786 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
787 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
788 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
789 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
790 /// let movie_index = client.index("add_documents_ndjson");
791 ///
792 /// let task = movie_index.add_documents_ndjson(
793 /// r#"{ "id": 1, "body": "doggo" }
794 /// { "id": 2, "body": "catto" }"#.as_bytes(),
795 /// Some("id"),
796 /// ).await.unwrap();
797 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
798 /// client.wait_for_task(task, None, None).await.unwrap();
799 ///
800 /// let movies = movie_index.get_documents::<serde_json::Value>().await.unwrap();
801 /// assert_eq!(movies.results.len(), 2);
802 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
803 /// # });
804 /// ```
805 #[cfg(not(target_arch = "wasm32"))]
806 pub async fn add_documents_ndjson<T: futures_io::AsyncRead + Send + Sync + 'static>(
807 &self,
808 payload: T,
809 primary_key: Option<&str>,
810 ) -> Result<TaskInfo, Error> {
811 self.add_or_replace_unchecked_payload(payload, "application/x-ndjson", primary_key)
812 .await
813 }
814
815 /// Add a raw csv payload and update them if they already exist.
816 ///
817 /// It configures the correct content type for csv data.
818 ///
819 /// If you send an already existing document (same id) the old document will be only partially updated according to the fields of the new document.
820 /// Thus, any fields not present in the new document are kept and remained unchanged.
821 ///
822 /// To completely overwrite a document, check out the [`Index::add_documents_csv`] documents method.
823 ///
824 /// # Example
825 ///
826 /// ```
827 /// # use serde::{Serialize, Deserialize};
828 /// # use meilisearch_sdk::{client::*, indexes::*};
829 /// # use std::thread::sleep;
830 /// # use std::time::Duration;
831 /// #
832 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
833 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
834 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
835 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
836 /// let movie_index = client.index("update_documents_csv");
837 ///
838 /// let task = movie_index.update_documents_csv(
839 /// "id,body\n1,\"doggo\"\n2,\"catto\"".as_bytes(),
840 /// Some("id"),
841 /// ).await.unwrap();
842 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
843 /// client.wait_for_task(task, None, None).await.unwrap();
844 ///
845 /// let movies = movie_index.get_documents::<serde_json::Value>().await.unwrap();
846 /// assert_eq!(movies.results.len(), 2);
847 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
848 /// # });
849 /// ```
850 #[cfg(not(target_arch = "wasm32"))]
851 pub async fn update_documents_csv<T: futures_io::AsyncRead + Send + Sync + 'static>(
852 &self,
853 payload: T,
854 primary_key: Option<&str>,
855 ) -> Result<TaskInfo, Error> {
856 self.add_or_update_unchecked_payload(payload, "text/csv", primary_key)
857 .await
858 }
859
860 /// Add a raw csv payload to meilisearch.
861 ///
862 /// It configures the correct content type for csv data.
863 ///
864 /// If you send an already existing document (same id) the **whole existing document** will be overwritten by the new document.
865 /// Fields previously in the document not present in the new document are removed.
866 ///
867 /// For a partial update of the document see [`Index::update_documents_csv`].
868 ///
869 /// # Example
870 ///
871 /// ```
872 /// # use serde::{Serialize, Deserialize};
873 /// # use meilisearch_sdk::{client::*, indexes::*};
874 /// # use std::thread::sleep;
875 /// # use std::time::Duration;
876 /// #
877 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
878 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
879 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
880 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
881 /// let movie_index = client.index("add_documents_csv");
882 ///
883 /// let task = movie_index.add_documents_csv(
884 /// "id,body\n1,\"doggo\"\n2,\"catto\"".as_bytes(),
885 /// Some("id"),
886 /// ).await.unwrap();
887 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
888 /// client.wait_for_task(task, None, None).await.unwrap();
889 ///
890 /// let movies = movie_index.get_documents::<serde_json::Value>().await.unwrap();
891 /// assert_eq!(movies.results.len(), 2);
892 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
893 /// # });
894 /// ```
895 #[cfg(not(target_arch = "wasm32"))]
896 pub async fn add_documents_csv<T: futures_io::AsyncRead + Send + Sync + 'static>(
897 &self,
898 payload: T,
899 primary_key: Option<&str>,
900 ) -> Result<TaskInfo, Error> {
901 self.add_or_replace_unchecked_payload(payload, "text/csv", primary_key)
902 .await
903 }
904
905 /// Add a list of documents and update them if they already exist.
906 ///
907 /// If you send an already existing document (same id) the old document will be only partially updated according to the fields of the new document.
908 /// Thus, any fields not present in the new document are kept and remained unchanged.
909 ///
910 /// To completely overwrite a document, check out the [`Index::add_or_replace`] documents method.
911 ///
912 /// # Example
913 ///
914 /// ```
915 /// # use serde::{Serialize, Deserialize};
916 /// # use meilisearch_sdk::client::*;
917 /// # use std::thread::sleep;
918 /// # use std::time::Duration;
919 /// #
920 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
921 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
922 /// #
923 /// #[derive(Serialize, Deserialize, Debug)]
924 /// struct Movie {
925 /// name: String,
926 /// description: String,
927 /// }
928 ///
929 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
930 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
931 /// let movie_index = client.index("add_or_update");
932 ///
933 /// let task = movie_index.add_or_update(&[
934 /// Movie {
935 /// name: String::from("Interstellar"),
936 /// description: String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")
937 /// },
938 /// Movie {
939 /// // note that the id field can only take alphanumerics characters (and '-' and '/')
940 /// name: String::from("MrsDoubtfire"),
941 /// description: String::from("Loving but irresponsible dad Daniel Hillard, estranged from his exasperated spouse, is crushed by a court order allowing only weekly visits with his kids. When Daniel learns his ex needs a housekeeper, he gets the job -- disguised as an English nanny. Soon he becomes not only his children's best pal but the kind of parent he should have been from the start.")
942 /// },
943 /// Movie {
944 /// name: String::from("Apollo13"),
945 /// description: String::from("The true story of technical troubles that scuttle the Apollo 13 lunar mission in 1971, risking the lives of astronaut Jim Lovell and his crew, with the failed journey turning into a thrilling saga of heroism. Drifting more than 200,000 miles from Earth, the astronauts work furiously with the ground crew to avert tragedy.")
946 /// },
947 /// ], Some("name")).await.unwrap();
948 ///
949 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
950 /// client.wait_for_task(task, None, None).await.unwrap();
951 ///
952 /// let movies = movie_index.get_documents::<Movie>().await.unwrap();
953 /// assert!(movies.results.len() >= 3);
954 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
955 /// # });
956 /// ```
957 pub async fn add_or_update<T: Serialize + Send + Sync>(
958 &self,
959 documents: &[T],
960 primary_key: Option<&str>,
961 ) -> Result<TaskInfo, Error> {
962 let url = if let Some(primary_key) = primary_key {
963 format!(
964 "{}/indexes/{}/documents?primaryKey={}",
965 self.client.host, self.uid, primary_key
966 )
967 } else {
968 format!("{}/indexes/{}/documents", self.client.host, self.uid)
969 };
970 self.client
971 .http_client
972 .request::<(), &[T], TaskInfo>(
973 &url,
974 Method::Put {
975 query: (),
976 body: documents,
977 },
978 202,
979 )
980 .await
981 }
982
983 /// Add a raw and unchecked payload to meilisearch.
984 ///
985 /// This can be useful if your application is only forwarding data from other sources.
986 ///
987 /// If you send an already existing document (same id) the old document will be only partially updated according to the fields of the new document.
988 /// Thus, any fields not present in the new document are kept and remained unchanged.
989 ///
990 /// To completely overwrite a document, check out the [`Index::add_or_replace_unchecked_payload`] documents method.
991 ///
992 /// # Example
993 ///
994 /// ```
995 /// # use serde::{Serialize, Deserialize};
996 /// # use meilisearch_sdk::{client::*, indexes::*};
997 /// # use std::thread::sleep;
998 /// # use std::time::Duration;
999 /// #
1000 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1001 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1002 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1003 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1004 /// let movie_index = client.index("add_or_replace_unchecked_payload");
1005 ///
1006 /// let task = movie_index.add_or_update_unchecked_payload(
1007 /// r#"{ "id": 1, "body": "doggo" }
1008 /// { "id": 2, "body": "catto" }"#.as_bytes(),
1009 /// "application/x-ndjson",
1010 /// Some("id"),
1011 /// ).await.unwrap();
1012 /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed
1013 /// client.wait_for_task(task, None, None).await.unwrap();
1014 ///
1015 /// let movies = movie_index.get_documents::<serde_json::Value>().await.unwrap();
1016 ///
1017 /// assert_eq!(movies.results.len(), 2);
1018 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1019 /// # });
1020 /// ```
1021 #[cfg(not(target_arch = "wasm32"))]
1022 pub async fn add_or_update_unchecked_payload<
1023 T: futures_io::AsyncRead + Send + Sync + 'static,
1024 >(
1025 &self,
1026 payload: T,
1027 content_type: &str,
1028 primary_key: Option<&str>,
1029 ) -> Result<TaskInfo, Error> {
1030 let url = if let Some(primary_key) = primary_key {
1031 format!(
1032 "{}/indexes/{}/documents?primaryKey={}",
1033 self.client.host, self.uid, primary_key
1034 )
1035 } else {
1036 format!("{}/indexes/{}/documents", self.client.host, self.uid)
1037 };
1038 self.client
1039 .http_client
1040 .stream_request::<(), T, TaskInfo>(
1041 &url,
1042 Method::Put {
1043 query: (),
1044 body: payload,
1045 },
1046 content_type,
1047 202,
1048 )
1049 .await
1050 }
1051
1052 /// Delete all documents in the [Index].
1053 ///
1054 /// # Example
1055 ///
1056 /// ```
1057 /// # use serde::{Serialize, Deserialize};
1058 /// # use meilisearch_sdk::{client::*, indexes::*};
1059 /// #
1060 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1061 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1062 /// #
1063 /// # #[derive(Serialize, Deserialize, Debug)]
1064 /// # struct Movie {
1065 /// # name: String,
1066 /// # description: String,
1067 /// # }
1068 /// #
1069 /// #
1070 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1071 /// #
1072 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1073 /// let movie_index = client.index("delete_all_documents");
1074 /// #
1075 /// # movie_index.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1076 /// #
1077 /// movie_index.delete_all_documents()
1078 /// .await
1079 /// .unwrap()
1080 /// .wait_for_completion(&client, None, None)
1081 /// .await
1082 /// .unwrap();
1083 /// let movies = movie_index.get_documents::<Movie>().await.unwrap();
1084 /// assert_eq!(movies.results.len(), 0);
1085 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1086 /// # });
1087 /// ```
1088 pub async fn delete_all_documents(&self) -> Result<TaskInfo, Error> {
1089 self.client
1090 .http_client
1091 .request::<(), (), TaskInfo>(
1092 &format!("{}/indexes/{}/documents", self.client.host, self.uid),
1093 Method::Delete { query: () },
1094 202,
1095 )
1096 .await
1097 }
1098
1099 /// Delete one document based on its unique id.
1100 ///
1101 /// # Example
1102 ///
1103 /// ```
1104 /// # use serde::{Serialize, Deserialize};
1105 /// # use meilisearch_sdk::client::*;
1106 /// #
1107 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1108 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1109 /// #
1110 /// # #[derive(Serialize, Deserialize, Debug)]
1111 /// # struct Movie {
1112 /// # name: String,
1113 /// # description: String,
1114 /// # }
1115 /// #
1116 /// #
1117 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1118 /// #
1119 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1120 /// let mut movies = client.index("delete_document");
1121 /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1122 /// // add a document with id = Interstellar
1123 /// movies.delete_document("Interstellar")
1124 /// .await
1125 /// .unwrap()
1126 /// .wait_for_completion(&client, None, None)
1127 /// .await
1128 /// .unwrap();
1129 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1130 /// # });
1131 /// ```
1132 pub async fn delete_document<T: Display>(&self, uid: T) -> Result<TaskInfo, Error> {
1133 self.client
1134 .http_client
1135 .request::<(), (), TaskInfo>(
1136 &format!(
1137 "{}/indexes/{}/documents/{}",
1138 self.client.host, self.uid, uid
1139 ),
1140 Method::Delete { query: () },
1141 202,
1142 )
1143 .await
1144 }
1145
1146 /// Delete a selection of documents based on array of document id's.
1147 ///
1148 /// # Example
1149 ///
1150 /// ```
1151 /// # use serde::{Serialize, Deserialize};
1152 /// # use meilisearch_sdk::client::*;
1153 /// #
1154 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1155 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1156 /// #
1157 /// # #[derive(Serialize, Deserialize, Debug)]
1158 /// # struct Movie {
1159 /// # name: String,
1160 /// # description: String,
1161 /// # }
1162 /// #
1163 /// #
1164 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1165 /// #
1166 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1167 /// let movies = client.index("delete_documents");
1168 /// #
1169 /// # // add some documents
1170 /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")},Movie{name:String::from("Unknown"), description:String::from("Unknown")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1171 /// #
1172 /// // delete some documents
1173 /// movies.delete_documents(&["Interstellar", "Unknown"])
1174 /// .await
1175 /// .unwrap()
1176 /// .wait_for_completion(&client, None, None)
1177 /// .await
1178 /// .unwrap();
1179 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1180 /// # });
1181 /// ```
1182 pub async fn delete_documents<T: Display + Serialize + std::fmt::Debug + Send + Sync>(
1183 &self,
1184 uids: &[T],
1185 ) -> Result<TaskInfo, Error> {
1186 self.client
1187 .http_client
1188 .request::<(), &[T], TaskInfo>(
1189 &format!(
1190 "{}/indexes/{}/documents/delete-batch",
1191 self.client.host, self.uid
1192 ),
1193 Method::Post {
1194 query: (),
1195 body: uids,
1196 },
1197 202,
1198 )
1199 .await
1200 }
1201
1202 /// Delete a selection of documents with filters.
1203 ///
1204 /// # Example
1205 ///
1206 /// ```
1207 /// # use serde::{Serialize, Deserialize};
1208 /// # use meilisearch_sdk::{client::*, documents::*};
1209 /// #
1210 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1211 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1212 /// #
1213 /// # #[derive(Serialize, Deserialize, Debug)]
1214 /// # struct Movie {
1215 /// # name: String,
1216 /// # id: String,
1217 /// # }
1218 /// #
1219 /// #
1220 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1221 /// #
1222 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1223 /// let index = client.index("delete_documents_with");
1224 /// #
1225 /// # index.set_filterable_attributes(["id"]);
1226 /// # // add some documents
1227 /// # index.add_or_replace(&[Movie{id:String::from("1"), name: String::from("First movie") }, Movie{id:String::from("1"), name: String::from("First movie") }], Some("id")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1228 ///
1229 /// let mut query = DocumentDeletionQuery::new(&index);
1230 /// query.with_filter("id = 1");
1231 /// // delete some documents
1232 /// index.delete_documents_with(&query)
1233 /// .await
1234 /// .unwrap()
1235 /// .wait_for_completion(&client, None, None)
1236 /// .await
1237 /// .unwrap();
1238 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1239 /// # });
1240 /// ```
1241 pub async fn delete_documents_with(
1242 &self,
1243 query: &DocumentDeletionQuery<'_, Http>,
1244 ) -> Result<TaskInfo, Error> {
1245 self.client
1246 .http_client
1247 .request::<(), &DocumentDeletionQuery<Http>, TaskInfo>(
1248 &format!("{}/indexes/{}/documents/delete", self.client.host, self.uid),
1249 Method::Post {
1250 query: (),
1251 body: query,
1252 },
1253 202,
1254 )
1255 .await
1256 }
1257
1258 /// Alias for the [`Index::update`] method.
1259 pub async fn set_primary_key(
1260 &mut self,
1261 primary_key: impl AsRef<str>,
1262 ) -> Result<TaskInfo, Error> {
1263 self.primary_key = Some(primary_key.as_ref().to_string());
1264
1265 self.update().await
1266 }
1267
1268 /// Fetch the information of the index as a raw JSON [Index], this index should already exist.
1269 ///
1270 /// If you use it directly from the [Client], you can use the method [`Client::get_raw_index`], which is the equivalent method from the client.
1271 ///
1272 /// # Example
1273 ///
1274 /// ```
1275 /// # use meilisearch_sdk::{client::*, indexes::*};
1276 /// #
1277 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1278 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1279 /// #
1280 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1281 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1282 /// # let index = client.create_index("fetch_info", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
1283 /// let mut idx = client.index("fetch_info");
1284 /// idx.fetch_info().await.unwrap();
1285 ///
1286 /// println!("{idx:?}");
1287 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1288 /// # });
1289 /// ```
1290 pub async fn fetch_info(&mut self) -> Result<(), Error> {
1291 let v = self.client.get_raw_index(&self.uid).await?;
1292 *self = Index::from_value(v, self.client.clone())?;
1293 Ok(())
1294 }
1295
1296 /// Fetch the primary key of the index.
1297 ///
1298 /// # Example
1299 ///
1300 /// ```
1301 /// # use meilisearch_sdk::{client::*, indexes::*};
1302 /// #
1303 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1304 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1305 /// #
1306 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1307 /// # // create the client
1308 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1309 /// let mut index = client.create_index("get_primary_key", Some("id"))
1310 /// .await
1311 /// .unwrap()
1312 /// .wait_for_completion(&client, None, None)
1313 /// .await.unwrap()
1314 /// .try_make_index(&client)
1315 /// .unwrap();
1316 ///
1317 /// let primary_key = index.get_primary_key().await.unwrap();
1318 ///
1319 /// assert_eq!(primary_key, Some("id"));
1320 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1321 /// # });
1322 /// ```
1323 pub async fn get_primary_key(&mut self) -> Result<Option<&str>, Error> {
1324 self.fetch_info().await?;
1325 Ok(self.primary_key.as_deref())
1326 }
1327
1328 /// Compact this index to reduce disk usage.
1329 ///
1330 /// Triggers a compaction task for the current index. Once completed, the
1331 /// index data is compacted on disk.
1332 ///
1333 /// # Example
1334 ///
1335 /// ```
1336 /// # use meilisearch_sdk::{client::*, indexes::*, tasks::Task};
1337 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1338 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1339 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1340 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1341 /// # let index = client
1342 /// # .create_index("compact_example", None)
1343 /// # .await
1344 /// # .unwrap()
1345 /// # .wait_for_completion(&client, None, None)
1346 /// # .await
1347 /// # .unwrap()
1348 /// # .try_make_index(&client)
1349 /// # .unwrap();
1350 ///
1351 /// let task = index
1352 /// .compact()
1353 /// .await
1354 /// .unwrap()
1355 /// .wait_for_completion(&client, None, None)
1356 /// .await
1357 /// .unwrap();
1358 ///
1359 /// assert!(matches!(task, Task::Succeeded { .. }));
1360 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1361 /// # });
1362 /// ```
1363 pub async fn compact(&self) -> Result<TaskInfo, Error> {
1364 self.client
1365 .http_client
1366 .request::<(), (), TaskInfo>(
1367 &format!("{}/indexes/{}/compact", self.client.host, self.uid),
1368 Method::Post {
1369 query: (),
1370 body: (),
1371 },
1372 202,
1373 )
1374 .await
1375 }
1376
1377 /// Get a [Task] from a specific [Index] to keep track of [asynchronous operations](https://www.meilisearch.com/docs/learn/advanced/asynchronous_operations).
1378 ///
1379 /// # Example
1380 ///
1381 /// ```
1382 /// # use serde::{Serialize, Deserialize};
1383 /// # use std::thread::sleep;
1384 /// # use std::time::Duration;
1385 /// # use meilisearch_sdk::{client::*, indexes::*, tasks::Task};
1386 /// #
1387 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1388 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1389 /// #
1390 /// # #[derive(Debug, Serialize, Deserialize, PartialEq)]
1391 /// # struct Document {
1392 /// # id: usize,
1393 /// # value: String,
1394 /// # kind: String,
1395 /// # }
1396 /// #
1397 /// #
1398 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1399 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1400 /// let movies = client.index("get_task");
1401 ///
1402 /// let task = movies.add_documents(&[
1403 /// Document { id: 0, kind: "title".into(), value: "The Social Network".to_string() }
1404 /// ], None).await.unwrap();
1405 /// # task.clone().wait_for_completion(&client, None, None).await.unwrap();
1406 ///
1407 /// // Get task status from the index, using `uid`
1408 /// let status = movies.get_task(&task).await.unwrap();
1409 ///
1410 /// let from_index = match status {
1411 /// Task::Enqueued { content } => content.uid,
1412 /// Task::Processing { content } => content.uid,
1413 /// Task::Failed { content } => content.task.uid,
1414 /// Task::Succeeded { content } => content.uid,
1415 /// };
1416 ///
1417 /// assert_eq!(task.get_task_uid(), from_index);
1418 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1419 /// # });
1420 /// ```
1421 pub async fn get_task(&self, uid: impl AsRef<u32>) -> Result<Task, Error> {
1422 self.client
1423 .http_client
1424 .request::<(), (), Task>(
1425 &format!("{}/tasks/{}", self.client.host, uid.as_ref()),
1426 Method::Get { query: () },
1427 200,
1428 )
1429 .await
1430 }
1431
1432 /// Get the status of all tasks in a given index.
1433 ///
1434 /// # Example
1435 ///
1436 /// ```
1437 /// # use serde::{Serialize, Deserialize};
1438 /// # use meilisearch_sdk::{client::*, indexes::*};
1439 /// #
1440 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1441 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1442 /// #
1443 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1444 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1445 /// # let index = client.create_index("get_tasks", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
1446 /// let tasks = index.get_tasks().await.unwrap();
1447 ///
1448 /// assert!(tasks.results.len() > 0);
1449 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1450 /// # });
1451 /// ```
1452 pub async fn get_tasks(&self) -> Result<TasksResults, Error> {
1453 let mut query = TasksSearchQuery::new(&self.client);
1454 query.with_index_uids([self.uid.as_str()]);
1455
1456 self.client.get_tasks_with(&query).await
1457 }
1458
1459 /// Get the status of all tasks in a given index.
1460 ///
1461 /// # Example
1462 ///
1463 /// ```
1464 /// # use serde::{Serialize, Deserialize};
1465 /// # use meilisearch_sdk::{client::*, indexes::*, tasks::*};
1466 /// #
1467 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1468 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1469 /// #
1470 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1471 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1472 /// # let index = client.create_index("get_tasks_with", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
1473 /// let mut query = TasksSearchQuery::new(&client);
1474 /// query.with_index_uids(["none_existant"]);
1475 ///
1476 /// let tasks = index.get_tasks_with(&query).await.unwrap();
1477 ///
1478 /// assert!(tasks.results.len() > 0);
1479 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1480 /// # });
1481 /// ```
1482 pub async fn get_tasks_with(
1483 &self,
1484 tasks_query: &TasksQuery<'_, TasksPaginationFilters, Http>,
1485 ) -> Result<TasksResults, Error> {
1486 let mut query = tasks_query.clone();
1487 query.with_index_uids([self.uid.as_str()]);
1488
1489 self.client.get_tasks_with(&query).await
1490 }
1491
1492 /// Get stats of an index.
1493 ///
1494 /// # Example
1495 ///
1496 /// ```
1497 /// # use meilisearch_sdk::{client::*, indexes::*};
1498 /// #
1499 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1500 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1501 /// #
1502 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1503 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1504 /// # let index = client.create_index("get_stats", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
1505 /// let stats = index.get_stats().await.unwrap();
1506 ///
1507 /// assert_eq!(stats.is_indexing, false);
1508 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1509 /// # });
1510 /// ```
1511 pub async fn get_stats(&self) -> Result<IndexStats, Error> {
1512 self.client
1513 .http_client
1514 .request::<(), (), IndexStats>(
1515 &format!("{}/indexes/{}/stats", self.client.host, self.uid),
1516 Method::Get { query: () },
1517 200,
1518 )
1519 .await
1520 }
1521
1522 /// Wait until Meilisearch processes a [Task], and get its status.
1523 ///
1524 /// `interval` = The frequency at which the server should be polled. **Default = 50ms**
1525 ///
1526 /// `timeout` = The maximum time to wait for processing to complete. **Default = 5000ms**
1527 ///
1528 /// If the waited time exceeds `timeout` then an [`Error::Timeout`] will be returned.
1529 ///
1530 /// See also [`Client::wait_for_task`, `Task::wait_for_completion`].
1531 ///
1532 /// # Example
1533 ///
1534 /// ```
1535 /// # use meilisearch_sdk::{client::*, indexes::*, tasks::Task};
1536 /// # use serde::{Serialize, Deserialize};
1537 /// #
1538 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1539 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1540 /// #
1541 /// # #[derive(Debug, Serialize, Deserialize, PartialEq)]
1542 /// # struct Document {
1543 /// # id: usize,
1544 /// # value: String,
1545 /// # kind: String,
1546 /// # }
1547 /// #
1548 /// #
1549 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1550 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1551 /// let movies = client.index("movies_index_wait_for_task");
1552 ///
1553 /// let task = movies.add_documents(&[
1554 /// Document { id: 0, kind: "title".into(), value: "The Social Network".to_string() },
1555 /// Document { id: 1, kind: "title".into(), value: "Harry Potter and the Sorcerer's Stone".to_string() },
1556 /// ], None).await.unwrap();
1557 ///
1558 /// let status = movies.wait_for_task(task, None, None).await.unwrap();
1559 ///
1560 /// assert!(matches!(status, Task::Succeeded { .. }));
1561 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1562 /// # });
1563 /// ```
1564 pub async fn wait_for_task(
1565 &self,
1566 task_id: impl AsRef<u32>,
1567 interval: Option<Duration>,
1568 timeout: Option<Duration>,
1569 ) -> Result<Task, Error> {
1570 self.client.wait_for_task(task_id, interval, timeout).await
1571 }
1572
1573 /// Add documents to the index in batches.
1574 ///
1575 /// `documents` = A slice of documents
1576 ///
1577 /// `batch_size` = Optional parameter that allows you to specify the size of the batch
1578 ///
1579 /// **`batch_size` is 1000 by default**
1580 ///
1581 /// # Example
1582 ///
1583 /// ```
1584 /// # use serde::{Serialize, Deserialize};
1585 /// # use meilisearch_sdk::client::*;
1586 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1587 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1588 /// #
1589 /// #[derive(Serialize, Deserialize, Debug)]
1590 /// struct Movie {
1591 /// name: String,
1592 /// description: String,
1593 /// }
1594 ///
1595 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1596 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1597 /// let movie_index = client.index("add_documents_in_batches");
1598 ///
1599 /// let tasks = movie_index.add_documents_in_batches(&[
1600 /// Movie {
1601 /// name: String::from("Interstellar"),
1602 /// description: String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")
1603 /// },
1604 /// Movie {
1605 /// // note that the id field can only take alphanumerics characters (and '-' and '/')
1606 /// name: String::from("MrsDoubtfire"),
1607 /// description: String::from("Loving but irresponsible dad Daniel Hillard, estranged from his exasperated spouse, is crushed by a court order allowing only weekly visits with his kids. When Daniel learns his ex needs a housekeeper, he gets the job -- disguised as an English nanny. Soon he becomes not only his children's best pal but the kind of parent he should have been from the start.")
1608 /// },
1609 /// Movie {
1610 /// name: String::from("Apollo13"),
1611 /// description: String::from("The true story of technical troubles that scuttle the Apollo 13 lunar mission in 1971, risking the lives of astronaut Jim Lovell and his crew, with the failed journey turning into a thrilling saga of heroism. Drifting more than 200,000 miles from Earth, the astronauts work furiously with the ground crew to avert tragedy.")
1612 /// }],
1613 /// Some(1),
1614 /// Some("name")
1615 /// ).await.unwrap();
1616 ///
1617 /// client.wait_for_task(tasks.last().unwrap(), None, None).await.unwrap();
1618 ///
1619 /// let movies = movie_index.get_documents::<Movie>().await.unwrap();
1620 ///
1621 /// assert!(movies.results.len() >= 3);
1622 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None,
1623 /// # None).await.unwrap();
1624 /// # });
1625 /// ```
1626 pub async fn add_documents_in_batches<T: Serialize + Send + Sync>(
1627 &self,
1628 documents: &[T],
1629 batch_size: Option<usize>,
1630 primary_key: Option<&str>,
1631 ) -> Result<Vec<TaskInfo>, Error> {
1632 let mut task = Vec::with_capacity(documents.len());
1633 for document_batch in documents.chunks(batch_size.unwrap_or(1000)) {
1634 task.push(self.add_documents(document_batch, primary_key).await?);
1635 }
1636 Ok(task)
1637 }
1638
1639 /// Update documents to the index in batches.
1640 ///
1641 /// `documents` = A slice of documents
1642 ///
1643 /// `batch_size` = Optional parameter that allows you to specify the size of the batch
1644 ///
1645 /// **`batch_size` is 1000 by default**
1646 ///
1647 /// # Example
1648 ///
1649 /// ```
1650 /// # use serde::{Serialize, Deserialize};
1651 /// # use meilisearch_sdk::client::*;
1652 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1653 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1654 /// #
1655 /// #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
1656 /// struct Movie {
1657 /// name: String,
1658 /// description: String,
1659 /// }
1660 ///
1661 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1662 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1663 /// let movie_index = client.index("update_documents_in_batches");
1664 ///
1665 /// let tasks = movie_index.add_documents_in_batches(&[
1666 /// Movie {
1667 /// name: String::from("Interstellar"),
1668 /// description: String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")
1669 /// },
1670 /// Movie {
1671 /// // note that the id field can only take alphanumerics characters (and '-' and '/')
1672 /// name: String::from("MrsDoubtfire"),
1673 /// description: String::from("Loving but irresponsible dad Daniel Hillard, estranged from his exasperated spouse, is crushed by a court order allowing only weekly visits with his kids. When Daniel learns his ex needs a housekeeper, he gets the job -- disguised as an English nanny. Soon he becomes not only his children's best pal but the kind of parent he should have been from the start.")
1674 /// },
1675 /// Movie {
1676 /// name: String::from("Apollo13"),
1677 /// description: String::from("The true story of technical troubles that scuttle the Apollo 13 lunar mission in 1971, risking the lives of astronaut Jim Lovell and his crew, with the failed journey turning into a thrilling saga of heroism. Drifting more than 200,000 miles from Earth, the astronauts work furiously with the ground crew to avert tragedy.")
1678 /// }],
1679 /// Some(1),
1680 /// Some("name")
1681 /// ).await.unwrap();
1682 ///
1683 /// client.wait_for_task(tasks.last().unwrap(), None, None).await.unwrap();
1684 ///
1685 /// let movies = movie_index.get_documents::<Movie>().await.unwrap();
1686 /// assert!(movies.results.len() >= 3);
1687 ///
1688 /// let updated_movies = [
1689 /// Movie {
1690 /// name: String::from("Interstellar"),
1691 /// description: String::from("Updated!")
1692 /// },
1693 /// Movie {
1694 /// // note that the id field can only take alphanumerics characters (and '-' and '/')
1695 /// name: String::from("MrsDoubtfire"),
1696 /// description: String::from("Updated!")
1697 /// },
1698 /// Movie {
1699 /// name: String::from("Apollo13"),
1700 /// description: String::from("Updated!")
1701 /// }];
1702 ///
1703 /// let tasks = movie_index.update_documents_in_batches(&updated_movies, Some(1), None).await.unwrap();
1704 ///
1705 /// client.wait_for_task(tasks.last().unwrap(), None, None).await.unwrap();
1706 ///
1707 /// let movies_updated = movie_index.get_documents::<Movie>().await.unwrap();
1708 ///
1709 /// assert!(movies_updated.results.len() >= 3);
1710 /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None,
1711 /// # None).await.unwrap();
1712 /// # });
1713 /// ```
1714 pub async fn update_documents_in_batches<T: Serialize + Send + Sync>(
1715 &self,
1716 documents: &[T],
1717 batch_size: Option<usize>,
1718 primary_key: Option<&str>,
1719 ) -> Result<Vec<TaskInfo>, Error> {
1720 let mut task = Vec::with_capacity(documents.len());
1721 for document_batch in documents.chunks(batch_size.unwrap_or(1000)) {
1722 task.push(self.add_or_update(document_batch, primary_key).await?);
1723 }
1724 Ok(task)
1725 }
1726
1727 /// Get similar documents in the index.
1728 ///
1729 /// # Example
1730 ///
1731 /// ```no_run
1732 /// # use serde::{Serialize, Deserialize};
1733 /// # use meilisearch_sdk::{client::*, indexes::*, similar::*};
1734 /// #
1735 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1736 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1737 /// #
1738 /// # #[derive(Serialize, Deserialize, Debug)]
1739 /// # struct Movie {
1740 /// # name: String,
1741 /// # description: String,
1742 /// # }
1743 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1744 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1745 /// # let movies = client.index("similar_query");
1746 /// #
1747 /// let query = SimilarQuery::new(&movies, "1", "default");
1748 /// let results = movies.execute_similar_query::<Movie>(&query).await.unwrap();
1749 ///
1750 /// assert!(results.hits.len() > 0);
1751 /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1752 /// # });
1753 /// ```
1754 pub async fn execute_similar_query<T: 'static + DeserializeOwned + Send + Sync>(
1755 &self,
1756 body: &SimilarQuery<'_, Http>,
1757 ) -> Result<SimilarResults<T>, Error> {
1758 self.client
1759 .http_client
1760 .request::<(), &SimilarQuery<Http>, SimilarResults<T>>(
1761 &format!("{}/indexes/{}/similar", self.client.host, self.uid),
1762 Method::Post { body, query: () },
1763 200,
1764 )
1765 .await
1766 }
1767
1768 pub fn similar_search<'a>(
1769 &'a self,
1770 document_id: &'a str,
1771 index_name: &'a str,
1772 ) -> SimilarQuery<'a, Http> {
1773 SimilarQuery::new(self, document_id, index_name)
1774 }
1775}
1776
1777impl<Http: HttpClient> AsRef<str> for Index<Http> {
1778 fn as_ref(&self) -> &str {
1779 &self.uid
1780 }
1781}
1782
1783/// An [`IndexUpdater`] used to update the specifics of an index.
1784///
1785/// # Example
1786///
1787/// ```
1788/// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}};
1789/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1790/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1791/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1792/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1793/// # let index = client
1794/// # .create_index("index_updater", None)
1795/// # .await
1796/// # .unwrap()
1797/// # .wait_for_completion(&client, None, None)
1798/// # .await
1799/// # .unwrap()
1800/// # // Once the task finished, we try to create an `Index` out of it
1801/// # .try_make_index(&client)
1802/// # .unwrap();
1803/// let task = IndexUpdater::new("index_updater", &client)
1804/// .with_primary_key("special_id")
1805/// .execute()
1806/// .await
1807/// .unwrap()
1808/// .wait_for_completion(&client, None, None)
1809/// .await
1810/// .unwrap();
1811///
1812/// let index = client.get_index("index_updater").await.unwrap();
1813///
1814/// assert_eq!(index.primary_key, Some("special_id".to_string()));
1815/// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1816/// # });
1817/// ```
1818#[derive(Debug, Serialize, Clone)]
1819#[serde(rename_all = "camelCase")]
1820pub struct IndexUpdater<'a, Http: HttpClient> {
1821 #[serde(skip)]
1822 pub client: &'a Client<Http>,
1823 #[serde(skip_serializing)]
1824 pub uid: String,
1825 /// New uid to rename the index to
1826 #[serde(rename = "uid", skip_serializing_if = "Option::is_none")]
1827 pub new_uid: Option<String>,
1828 pub primary_key: Option<String>,
1829}
1830
1831impl<'a, Http: HttpClient> IndexUpdater<'a, Http> {
1832 pub fn new(uid: impl AsRef<str>, client: &Client<Http>) -> IndexUpdater<'_, Http> {
1833 IndexUpdater {
1834 client,
1835 primary_key: None,
1836 uid: uid.as_ref().to_string(),
1837 new_uid: None,
1838 }
1839 }
1840 /// Define the new `primary_key` to set on the [Index].
1841 ///
1842 /// # Example
1843 ///
1844 /// ```
1845 /// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}};
1846 /// #
1847 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1848 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1849 /// #
1850 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1851 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1852 /// # let index = client
1853 /// # .create_index("index_updater_with_primary_key", None)
1854 /// # .await
1855 /// # .unwrap()
1856 /// # .wait_for_completion(&client, None, None)
1857 /// # .await
1858 /// # .unwrap()
1859 /// # // Once the task finished, we try to create an `Index` out of it
1860 /// # .try_make_index(&client)
1861 /// # .unwrap();
1862 /// let task = IndexUpdater::new("index_updater_with_primary_key", &client)
1863 /// .with_primary_key("special_id")
1864 /// .execute()
1865 /// .await
1866 /// .unwrap()
1867 /// .wait_for_completion(&client, None, None)
1868 /// .await
1869 /// .unwrap();
1870 ///
1871 /// let index = client.get_index("index_updater_with_primary_key").await.unwrap();
1872 ///
1873 /// assert_eq!(index.primary_key, Some("special_id".to_string()));
1874 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1875 /// # });
1876 /// ```
1877 pub fn with_primary_key(
1878 &mut self,
1879 primary_key: impl AsRef<str>,
1880 ) -> &mut IndexUpdater<'a, Http> {
1881 self.primary_key = Some(primary_key.as_ref().to_string());
1882 self
1883 }
1884
1885 /// Define a new `uid` to rename the index.
1886 pub fn with_uid(&mut self, new_uid: impl AsRef<str>) -> &mut IndexUpdater<'a, Http> {
1887 self.new_uid = Some(new_uid.as_ref().to_string());
1888 self
1889 }
1890
1891 /// Alias for `with_uid` with clearer intent.
1892 pub fn with_new_uid(&mut self, new_uid: impl AsRef<str>) -> &mut IndexUpdater<'a, Http> {
1893 self.with_uid(new_uid)
1894 }
1895
1896 /// Execute the update of an [Index] using the [`IndexUpdater`].
1897 ///
1898 /// # Example
1899 ///
1900 /// ```
1901 /// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}};
1902 /// #
1903 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1904 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1905 /// #
1906 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1907 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1908 /// # let index = client
1909 /// # .create_index("index_updater_execute", None)
1910 /// # .await
1911 /// # .unwrap()
1912 /// # .wait_for_completion(&client, None, None)
1913 /// # .await
1914 /// # .unwrap()
1915 /// # // Once the task finished, we try to create an `Index` out of it
1916 /// # .try_make_index(&client)
1917 /// # .unwrap();
1918 /// let task = IndexUpdater::new("index_updater_execute", &client)
1919 /// .with_primary_key("special_id")
1920 /// .execute()
1921 /// .await
1922 /// .unwrap()
1923 /// .wait_for_completion(&client, None, None)
1924 /// .await
1925 /// .unwrap();
1926 ///
1927 /// let index = client.get_index("index_updater_execute").await.unwrap();
1928 ///
1929 /// assert_eq!(index.primary_key, Some("special_id".to_string()));
1930 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
1931 /// # });
1932 /// ```
1933 pub async fn execute(&'a self) -> Result<TaskInfo, Error> {
1934 self.client
1935 .http_client
1936 .request::<(), &IndexUpdater<Http>, TaskInfo>(
1937 &format!("{}/indexes/{}", self.client.host, self.uid),
1938 Method::Patch {
1939 query: (),
1940 body: self,
1941 },
1942 202,
1943 )
1944 .await
1945 }
1946}
1947
1948impl<Http: HttpClient> AsRef<str> for IndexUpdater<'_, Http> {
1949 fn as_ref(&self) -> &str {
1950 &self.uid
1951 }
1952}
1953
1954impl<'a, Http: HttpClient> AsRef<IndexUpdater<'a, Http>> for IndexUpdater<'a, Http> {
1955 fn as_ref(&self) -> &IndexUpdater<'a, Http> {
1956 self
1957 }
1958}
1959
1960#[derive(Debug, Clone, Deserialize)]
1961#[serde(rename_all = "camelCase")]
1962pub struct IndexStats {
1963 /// Total number of documents in an index
1964 pub number_of_documents: usize,
1965
1966 /// Total number of documents with at least one embedding
1967 pub number_of_embedded_documents: usize,
1968
1969 /// Total number of embeddings in an index
1970 pub number_of_embeddings: usize,
1971
1972 /// Storage space claimed by all documents in the index in bytes
1973 pub raw_document_db_size: usize,
1974
1975 /// Total size of the documents stored in an index divided by the number of documents in that same index
1976 pub avg_document_size: usize,
1977
1978 /// If `true`, the index is still processing documents and attempts to search will yield impredictable results
1979 pub is_indexing: bool,
1980
1981 /// Shows every field in the index along with the total number of documents containing that field in said index
1982 pub field_distribution: HashMap<String, usize>,
1983}
1984
1985/// An [`IndexesQuery`] containing filter and pagination parameters when searching for [Indexes](Index).
1986///
1987/// # Example
1988///
1989/// ```
1990/// # use meilisearch_sdk::{client::*, indexes::*};
1991/// #
1992/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1993/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1994/// #
1995/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1996/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1997/// # let index = client
1998/// # .create_index("index_query_builder", None)
1999/// # .await
2000/// # .unwrap()
2001/// # .wait_for_completion(&client, None, None)
2002/// # .await
2003/// # .unwrap()
2004/// # // Once the task finished, we try to create an `Index` out of it.
2005/// # .try_make_index(&client)
2006/// # .unwrap();
2007/// let mut indexes = IndexesQuery::new(&client)
2008/// .with_limit(1)
2009/// .execute().await.unwrap();
2010///
2011/// assert_eq!(indexes.results.len(), 1);
2012/// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
2013/// # });
2014/// ```
2015#[derive(Debug, Serialize, Clone)]
2016#[serde(rename_all = "camelCase")]
2017pub struct IndexesQuery<'a, Http: HttpClient> {
2018 #[serde(skip_serializing)]
2019 pub client: &'a Client<Http>,
2020 /// The number of [Indexes](Index) to skip.
2021 ///
2022 /// If the value of the parameter `offset` is `n`, the `n` first indexes will not be returned.
2023 /// This is helpful for pagination.
2024 ///
2025 /// Example: If you want to skip the first index, set offset to `1`.
2026 #[serde(skip_serializing_if = "Option::is_none")]
2027 pub offset: Option<usize>,
2028
2029 /// The maximum number of [Indexes](Index) returned.
2030 ///
2031 /// If the value of the parameter `limit` is `n`, there will never be more than `n` indexes in the response.
2032 /// This is helpful for pagination.
2033 ///
2034 /// Example: If you don't want to get more than two indexes, set limit to `2`.
2035 ///
2036 /// **Default: `20`**
2037 #[serde(skip_serializing_if = "Option::is_none")]
2038 pub limit: Option<usize>,
2039}
2040
2041impl<'a, Http: HttpClient> IndexesQuery<'a, Http> {
2042 #[must_use]
2043 pub fn new(client: &Client<Http>) -> IndexesQuery<'_, Http> {
2044 IndexesQuery {
2045 client,
2046 offset: None,
2047 limit: None,
2048 }
2049 }
2050
2051 /// Specify the offset.
2052 ///
2053 /// # Example
2054 ///
2055 /// ```
2056 /// # use meilisearch_sdk::{client::*, indexes::*};
2057 /// #
2058 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
2059 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
2060 /// #
2061 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
2062 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
2063 /// # let index = client
2064 /// # .create_index("index_query_with_offset", None)
2065 /// # .await
2066 /// # .unwrap()
2067 /// # .wait_for_completion(&client, None, None)
2068 /// # .await
2069 /// # .unwrap()
2070 /// # // Once the task finished, we try to create an `Index` out of it
2071 /// # .try_make_index(&client)
2072 /// # .unwrap();
2073 /// let mut indexes = IndexesQuery::new(&client)
2074 /// .with_offset(1)
2075 /// .execute().await.unwrap();
2076 ///
2077 /// assert_eq!(indexes.offset, 1);
2078 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
2079 /// # });
2080 /// ```
2081 pub fn with_offset(&mut self, offset: usize) -> &mut IndexesQuery<'a, Http> {
2082 self.offset = Some(offset);
2083 self
2084 }
2085
2086 /// Specify the maximum number of [Indexes](Index) to return.
2087 ///
2088 /// # Example
2089 ///
2090 /// ```
2091 /// # use meilisearch_sdk::{client::*, indexes::*};
2092 /// #
2093 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
2094 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
2095 /// #
2096 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
2097 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
2098 /// # let index = client
2099 /// # .create_index("index_query_with_limit", None)
2100 /// # .await
2101 /// # .unwrap()
2102 /// # .wait_for_completion(&client, None, None)
2103 /// # .await
2104 /// # .unwrap()
2105 /// # // Once the task finished, we try to create an `Index` out of it
2106 /// # .try_make_index(&client)
2107 /// # .unwrap();
2108 /// let mut indexes = IndexesQuery::new(&client)
2109 /// .with_limit(1)
2110 /// .execute().await.unwrap();
2111 ///
2112 /// assert_eq!(indexes.results.len(), 1);
2113 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
2114 /// # });
2115 /// ```
2116 pub fn with_limit(&mut self, limit: usize) -> &mut IndexesQuery<'a, Http> {
2117 self.limit = Some(limit);
2118 self
2119 }
2120 /// Get [Indexes](Index).
2121 ///
2122 /// # Example
2123 ///
2124 /// ```
2125 /// # use meilisearch_sdk::{indexes::IndexesQuery, client::Client};
2126 /// #
2127 /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
2128 /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
2129 /// #
2130 /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
2131 /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
2132 /// # let index = client
2133 /// # .create_index("index_query_with_execute", None)
2134 /// # .await
2135 /// # .unwrap()
2136 /// # .wait_for_completion(&client, None, None)
2137 /// # .await
2138 /// # .unwrap()
2139 /// # // Once the task finished, we try to create an `Index` out of it
2140 /// # .try_make_index(&client)
2141 /// # .unwrap();
2142 /// let mut indexes = IndexesQuery::new(&client)
2143 /// .with_limit(1)
2144 /// .execute().await.unwrap();
2145 ///
2146 /// assert_eq!(indexes.results.len(), 1);
2147 /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
2148 /// # });
2149 /// ```
2150 pub async fn execute(&self) -> Result<IndexesResults<Http>, Error> {
2151 self.client.list_all_indexes_with(self).await
2152 }
2153}
2154
2155#[derive(Debug, Clone)]
2156pub struct IndexesResults<Http: HttpClient = DefaultHttpClient> {
2157 pub results: Vec<Index<Http>>,
2158 pub limit: u32,
2159 pub offset: u32,
2160 pub total: u32,
2161}
2162
2163#[cfg(test)]
2164mod tests {
2165 use super::*;
2166
2167 use big_s::S;
2168 use meilisearch_test_macro::meilisearch_test;
2169 use serde_json::json;
2170
2171 #[meilisearch_test]
2172 async fn test_from_value(client: Client) {
2173 let t = OffsetDateTime::now_utc();
2174 let trfc3339 = t
2175 .format(&time::format_description::well_known::Rfc3339)
2176 .unwrap();
2177
2178 let value = json!({
2179 "createdAt": &trfc3339,
2180 "primaryKey": null,
2181 "uid": "test_from_value",
2182 "updatedAt": &trfc3339,
2183 });
2184
2185 let idx = Index {
2186 uid: S("test_from_value"),
2187 primary_key: None,
2188 created_at: Some(t),
2189 updated_at: Some(t),
2190 client: client.clone(),
2191 };
2192
2193 let res = Index::from_value(value, client).unwrap();
2194
2195 assert_eq!(res.updated_at, idx.updated_at);
2196 assert_eq!(res.created_at, idx.created_at);
2197 assert_eq!(res.uid, idx.uid);
2198 assert_eq!(res.primary_key, idx.primary_key);
2199 assert_eq!(res.client.host, idx.client.host);
2200 assert_eq!(res.client.api_key, idx.client.api_key);
2201 }
2202
2203 #[meilisearch_test]
2204 async fn test_fetch_info(mut index: Index) {
2205 let res = index.fetch_info().await;
2206 assert!(res.is_ok());
2207 assert!(index.updated_at.is_some());
2208 assert!(index.created_at.is_some());
2209 assert!(index.primary_key.is_none());
2210 }
2211
2212 #[meilisearch_test]
2213 async fn test_get_documents(index: Index) {
2214 #[derive(Debug, Serialize, Deserialize, PartialEq)]
2215 struct Object {
2216 id: usize,
2217 value: String,
2218 kind: String,
2219 }
2220 let res = index.get_documents::<Object>().await.unwrap();
2221
2222 assert_eq!(res.limit, 20);
2223 }
2224
2225 #[meilisearch_test]
2226 async fn test_get_documents_with(index: Index) {
2227 #[derive(Debug, Serialize, Deserialize, PartialEq)]
2228 struct Object {
2229 id: usize,
2230 value: String,
2231 kind: String,
2232 }
2233
2234 let mut documents_query = DocumentsQuery::new(&index);
2235 documents_query.with_limit(1).with_offset(2);
2236
2237 let res = index
2238 .get_documents_with::<Object>(&documents_query)
2239 .await
2240 .unwrap();
2241
2242 assert_eq!(res.limit, 1);
2243 assert_eq!(res.offset, 2);
2244 }
2245
2246 #[meilisearch_test]
2247 async fn test_update_document_json(client: Client, index: Index) -> Result<(), Error> {
2248 let old_json = [
2249 json!({ "id": 1, "body": "doggo" }),
2250 json!({ "id": 2, "body": "catto" }),
2251 ];
2252 let updated_json = [
2253 json!({ "id": 1, "second_body": "second_doggo" }),
2254 json!({ "id": 2, "second_body": "second_catto" }),
2255 ];
2256
2257 let task = index
2258 .add_documents(&old_json, Some("id"))
2259 .await
2260 .unwrap()
2261 .wait_for_completion(&client, None, None)
2262 .await
2263 .unwrap();
2264 let _ = index.get_task(task).await?;
2265
2266 let task = index
2267 .add_or_update(&updated_json, None)
2268 .await
2269 .unwrap()
2270 .wait_for_completion(&client, None, None)
2271 .await
2272 .unwrap();
2273
2274 let status = index.get_task(task).await?;
2275 let elements = index.get_documents::<serde_json::Value>().await.unwrap();
2276
2277 assert!(matches!(status, Task::Succeeded { .. }));
2278 assert_eq!(elements.results.len(), 2);
2279
2280 let expected_result = vec![
2281 json!( {"body": "doggo", "id": 1, "second_body": "second_doggo"}),
2282 json!( {"body": "catto", "id": 2, "second_body": "second_catto"}),
2283 ];
2284
2285 assert_eq!(elements.results, expected_result);
2286
2287 Ok(())
2288 }
2289
2290 #[meilisearch_test]
2291 async fn test_rename_index_via_update(client: Client, name: String) -> Result<(), Error> {
2292 let from = format!("{name}_from");
2293 let to = format!("{name}_to");
2294
2295 // Create source index
2296 client
2297 .create_index(&from, None)
2298 .await?
2299 .wait_for_completion(&client, None, None)
2300 .await?;
2301
2302 // Rename using index update
2303 IndexUpdater::new(&from, &client)
2304 .with_uid(&to)
2305 .execute()
2306 .await?
2307 .wait_for_completion(&client, None, None)
2308 .await?;
2309
2310 // New index should exist
2311 let new_index = client.get_index(&to).await?;
2312 assert_eq!(new_index.uid, to);
2313
2314 // Old index should no longer exist
2315 let old_index = client.get_index(&from).await;
2316 assert!(old_index.is_err(), "old uid still resolves after rename");
2317
2318 // cleanup
2319 new_index
2320 .delete()
2321 .await?
2322 .wait_for_completion(&client, None, None)
2323 .await?;
2324
2325 // defensive cleanup if rename semantics change
2326 if let Ok(idx) = client.get_index(&from).await {
2327 idx.delete()
2328 .await?
2329 .wait_for_completion(&client, None, None)
2330 .await?;
2331 }
2332
2333 Ok(())
2334 }
2335
2336 #[meilisearch_test]
2337 async fn test_add_documents_ndjson(client: Client, index: Index) -> Result<(), Error> {
2338 let ndjson = r#"{ "id": 1, "body": "doggo" }{ "id": 2, "body": "catto" }"#.as_bytes();
2339
2340 let task = index
2341 .add_documents_ndjson(ndjson, Some("id"))
2342 .await?
2343 .wait_for_completion(&client, None, None)
2344 .await?;
2345
2346 let status = index.get_task(task).await?;
2347 let elements = index.get_documents::<serde_json::Value>().await.unwrap();
2348 assert!(matches!(status, Task::Succeeded { .. }));
2349 assert_eq!(elements.results.len(), 2);
2350
2351 Ok(())
2352 }
2353
2354 #[meilisearch_test]
2355 async fn test_update_documents_ndjson(client: Client, index: Index) -> Result<(), Error> {
2356 let old_ndjson = r#"{ "id": 1, "body": "doggo" }{ "id": 2, "body": "catto" }"#.as_bytes();
2357 let updated_ndjson =
2358 r#"{ "id": 1, "second_body": "second_doggo" }{ "id": 2, "second_body": "second_catto" }"#.as_bytes();
2359 // Add first njdson document
2360 let task = index
2361 .add_documents_ndjson(old_ndjson, Some("id"))
2362 .await?
2363 .wait_for_completion(&client, None, None)
2364 .await?;
2365 let _ = index.get_task(task).await?;
2366
2367 // Update via njdson document
2368 let task = index
2369 .update_documents_ndjson(updated_ndjson, Some("id"))
2370 .await?
2371 .wait_for_completion(&client, None, None)
2372 .await?;
2373
2374 let status = index.get_task(task).await?;
2375 let elements = index.get_documents::<serde_json::Value>().await.unwrap();
2376
2377 assert!(matches!(status, Task::Succeeded { .. }));
2378 assert_eq!(elements.results.len(), 2);
2379
2380 let expected_result = vec![
2381 json!( {"body": "doggo", "id": 1, "second_body": "second_doggo"}),
2382 json!( {"body": "catto", "id": 2, "second_body": "second_catto"}),
2383 ];
2384
2385 assert_eq!(elements.results, expected_result);
2386
2387 Ok(())
2388 }
2389
2390 #[meilisearch_test]
2391 async fn test_add_documents_csv(client: Client, index: Index) -> Result<(), Error> {
2392 let csv_input = "id,body\n1,\"doggo\"\n2,\"catto\"".as_bytes();
2393
2394 let task = index
2395 .add_documents_csv(csv_input, Some("id"))
2396 .await?
2397 .wait_for_completion(&client, None, None)
2398 .await?;
2399
2400 let status = index.get_task(task).await?;
2401 let elements = index.get_documents::<serde_json::Value>().await.unwrap();
2402 assert!(matches!(status, Task::Succeeded { .. }));
2403 assert_eq!(elements.results.len(), 2);
2404
2405 Ok(())
2406 }
2407
2408 #[meilisearch_test]
2409 async fn test_update_documents_csv(client: Client, index: Index) -> Result<(), Error> {
2410 let old_csv = "id,body\n1,\"doggo\"\n2,\"catto\"".as_bytes();
2411 let updated_csv = "id,body\n1,\"new_doggo\"\n2,\"new_catto\"".as_bytes();
2412 // Add first njdson document
2413 let task = index
2414 .add_documents_csv(old_csv, Some("id"))
2415 .await?
2416 .wait_for_completion(&client, None, None)
2417 .await?;
2418 let _ = index.get_task(task).await?;
2419
2420 // Update via njdson document
2421 let task = index
2422 .update_documents_csv(updated_csv, Some("id"))
2423 .await?
2424 .wait_for_completion(&client, None, None)
2425 .await?;
2426
2427 let status = index.get_task(task).await?;
2428 let elements = index.get_documents::<serde_json::Value>().await.unwrap();
2429
2430 assert!(matches!(status, Task::Succeeded { .. }));
2431 assert_eq!(elements.results.len(), 2);
2432
2433 let expected_result = vec![
2434 json!( {"body": "new_doggo", "id": "1"}),
2435 json!( {"body": "new_catto", "id": "2"}),
2436 ];
2437
2438 assert_eq!(elements.results, expected_result);
2439
2440 Ok(())
2441 }
2442 #[meilisearch_test]
2443
2444 async fn test_get_one_task(client: Client, index: Index) -> Result<(), Error> {
2445 let task = index
2446 .delete_all_documents()
2447 .await?
2448 .wait_for_completion(&client, None, None)
2449 .await?;
2450
2451 let status = index.get_task(task).await?;
2452
2453 match status {
2454 Task::Enqueued {
2455 content:
2456 EnqueuedTask {
2457 index_uid: Some(index_uid),
2458 ..
2459 },
2460 }
2461 | Task::Processing {
2462 content:
2463 ProcessingTask {
2464 index_uid: Some(index_uid),
2465 ..
2466 },
2467 }
2468 | Task::Failed {
2469 content:
2470 FailedTask {
2471 task:
2472 SucceededTask {
2473 index_uid: Some(index_uid),
2474 ..
2475 },
2476 ..
2477 },
2478 }
2479 | Task::Succeeded {
2480 content:
2481 SucceededTask {
2482 index_uid: Some(index_uid),
2483 ..
2484 },
2485 } => assert_eq!(index_uid, *index.uid),
2486 task => panic!(
2487 "The task should have an index_uid that is not null {:?}",
2488 task
2489 ),
2490 }
2491 Ok(())
2492 }
2493
2494 #[meilisearch_test]
2495 async fn test_compact_index_succeeds(client: Client, index: Index) -> Result<(), Error> {
2496 let task = index
2497 .compact()
2498 .await?
2499 .wait_for_completion(&client, None, None)
2500 .await?;
2501
2502 assert!(task.is_success());
2503 Ok(())
2504 }
2505}