meilisearch_sdk/
client.rs

1use serde::de::Error as SerdeError;
2use serde::{de::DeserializeOwned, Deserialize, Serialize};
3use serde_json::{json, Value};
4use std::{collections::HashMap, time::Duration};
5use time::OffsetDateTime;
6
7use crate::{
8    errors::*,
9    indexes::*,
10    key::{Key, KeyBuilder, KeyUpdater, KeysQuery, KeysResults},
11    network::{NetworkState, NetworkUpdate},
12    request::*,
13    search::*,
14    task_info::TaskInfo,
15    tasks::{Task, TasksCancelQuery, TasksDeleteQuery, TasksResults, TasksSearchQuery},
16    utils::SleepBackend,
17    webhooks::{WebhookCreate, WebhookInfo, WebhookList, WebhookUpdate},
18    DefaultHttpClient,
19};
20
21/// The top-level struct of the SDK, representing a client containing [indexes](../indexes/struct.Index.html).
22#[derive(Debug, Clone)]
23pub struct Client<Http: HttpClient = DefaultHttpClient> {
24    pub(crate) host: String,
25    pub(crate) api_key: Option<String>,
26    pub(crate) http_client: Http,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SwapIndexes {
31    pub indexes: (String, String),
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub rename: Option<bool>,
34}
35
36#[cfg(feature = "reqwest")]
37impl Client {
38    /// Create a client using the specified server.
39    ///
40    /// Don't put a '/' at the end of the host.
41    ///
42    /// In production mode, see [the documentation about authentication](https://www.meilisearch.com/docs/learn/security/master_api_keys#authentication).
43    ///
44    /// # Example
45    ///
46    /// ```
47    /// # use meilisearch_sdk::{client::*, indexes::*};
48    /// #
49    /// let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
50    /// let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
51    ///
52    /// let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
53    /// ```
54    pub fn new(
55        host: impl Into<String>,
56        api_key: Option<impl Into<String>>,
57    ) -> Result<Client, Error> {
58        let api_key = api_key.map(|key| key.into());
59        let http_client = crate::reqwest::ReqwestClient::new(api_key.as_deref())?;
60
61        Ok(Client {
62            host: host.into(),
63            api_key,
64            http_client,
65        })
66    }
67}
68
69impl<Http: HttpClient> Client<Http> {
70    // Create a client with a custom http client
71    pub fn new_with_client(
72        host: impl Into<String>,
73        api_key: Option<impl Into<String>>,
74        http_client: Http,
75    ) -> Client<Http> {
76        Client {
77            host: host.into(),
78            api_key: api_key.map(|key| key.into()),
79            http_client,
80        }
81    }
82
83    fn parse_indexes_results_from_value(
84        &self,
85        value: &Value,
86    ) -> Result<IndexesResults<Http>, Error> {
87        let raw_indexes = value["results"]
88            .as_array()
89            .ok_or_else(|| serde_json::Error::custom("Missing or invalid 'results' field"))
90            .map_err(Error::ParseError)?;
91
92        let limit = value["limit"]
93            .as_u64()
94            .ok_or_else(|| serde_json::Error::custom("Missing or invalid 'limit' field"))
95            .map_err(Error::ParseError)? as u32;
96
97        let offset = value["offset"]
98            .as_u64()
99            .ok_or_else(|| serde_json::Error::custom("Missing or invalid 'offset' field"))
100            .map_err(Error::ParseError)? as u32;
101
102        let total = value["total"]
103            .as_u64()
104            .ok_or_else(|| serde_json::Error::custom("Missing or invalid 'total' field"))
105            .map_err(Error::ParseError)? as u32;
106
107        let results = raw_indexes
108            .iter()
109            .map(|raw_index| Index::from_value(raw_index.clone(), self.clone()))
110            .collect::<Result<_, _>>()?;
111
112        let indexes_results = IndexesResults {
113            limit,
114            offset,
115            total,
116            results,
117        };
118
119        Ok(indexes_results)
120    }
121
122    pub async fn execute_multi_search_query<T: 'static + DeserializeOwned + Send + Sync>(
123        &self,
124        body: &MultiSearchQuery<'_, '_, Http>,
125    ) -> Result<MultiSearchResponse<T>, Error> {
126        self.http_client
127            .request::<(), &MultiSearchQuery<Http>, MultiSearchResponse<T>>(
128                &format!("{}/multi-search", &self.host),
129                Method::Post { body, query: () },
130                200,
131            )
132            .await
133    }
134
135    pub async fn execute_federated_multi_search_query<
136        T: 'static + DeserializeOwned + Send + Sync,
137    >(
138        &self,
139        body: &FederatedMultiSearchQuery<'_, '_, Http>,
140    ) -> Result<FederatedMultiSearchResponse<T>, Error> {
141        self.http_client
142            .request::<(), &FederatedMultiSearchQuery<Http>, FederatedMultiSearchResponse<T>>(
143                &format!("{}/multi-search", &self.host),
144                Method::Post { body, query: () },
145                200,
146            )
147            .await
148    }
149
150    /// Make multiple search requests.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// # use serde::{Serialize, Deserialize};
156    /// # use meilisearch_sdk::{client::*, indexes::*, search::*};
157    /// #
158    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
159    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
160    /// #
161    /// #[derive(Serialize, Deserialize, Debug)]
162    /// struct Movie {
163    ///     name: String,
164    ///     description: String,
165    /// }
166    ///
167    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
168    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
169    /// let mut movies = client.index("search");
170    /// # // add some documents
171    /// # 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();
172    ///
173    /// let search_query_1 = SearchQuery::new(&movies)
174    ///     .with_query("Interstellar")
175    ///     .build();
176    /// let search_query_2 = SearchQuery::new(&movies)
177    ///     .with_query("")
178    ///     .build();
179    ///
180    /// let response = client
181    ///     .multi_search()
182    ///     .with_search_query(search_query_1)
183    ///     .with_search_query(search_query_2)
184    ///     .execute::<Movie>()
185    ///     .await
186    ///     .unwrap();
187    ///
188    /// assert_eq!(response.results.len(), 2);
189    /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
190    /// # });
191    /// ```
192    ///
193    /// # Federated Search
194    ///
195    /// You can use [`MultiSearchQuery::with_federation`] to perform a [federated
196    /// search][1] where results from different indexes are merged and returned as
197    /// one list.
198    ///
199    /// When executing a federated query, the type parameter `T` is less clear,
200    /// as the documents in the different indexes potentially have different
201    /// fields and you might have one Rust type per index. In most cases, you
202    /// either want to create an enum with one variant per index and `#[serde
203    /// (untagged)]` attribute, or if you need more control, just pass
204    /// `serde_json::Map<String, serde_json::Value>` and then deserialize that
205    /// into the appropriate target types later.
206    ///
207    /// [1]: https://www.meilisearch.com/docs/learn/multi_search/multi_search_vs_federated_search#what-is-federated-search
208    #[must_use]
209    pub fn multi_search(&self) -> MultiSearchQuery<'_, '_, Http> {
210        MultiSearchQuery::new(self)
211    }
212
213    /// Return the host associated with this index.
214    ///
215    /// # Example
216    ///
217    /// ```
218    /// # use meilisearch_sdk::{client::*};
219    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
220    /// let client = Client::new("http://doggo.dog", Some(MEILISEARCH_API_KEY)).unwrap();
221    ///
222    /// assert_eq!(client.get_host(), "http://doggo.dog");
223    /// ```
224    #[must_use]
225    pub fn get_host(&self) -> &str {
226        &self.host
227    }
228
229    /// Return the api key associated with this index.
230    ///
231    /// # Example
232    ///
233    /// ```
234    /// # use meilisearch_sdk::{client::*};
235    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
236    /// let client = Client::new(MEILISEARCH_URL, Some("doggo")).unwrap();
237    ///
238    /// assert_eq!(client.get_api_key(), Some("doggo"));
239    /// ```
240    #[must_use]
241    pub fn get_api_key(&self) -> Option<&str> {
242        self.api_key.as_deref()
243    }
244
245    /// List all [Indexes](Index) with query parameters and return values as instances of [Index].
246    ///
247    /// # Example
248    ///
249    /// ```
250    /// # use meilisearch_sdk::{client::*, indexes::*};
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    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
256    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
257    /// let indexes: IndexesResults = client.list_all_indexes().await.unwrap();
258    ///
259    /// let indexes: IndexesResults = client.list_all_indexes().await.unwrap();
260    /// println!("{:?}", indexes);
261    /// # });
262    /// ```
263    pub async fn list_all_indexes(&self) -> Result<IndexesResults<Http>, Error> {
264        let value = self.list_all_indexes_raw().await?;
265        let indexes_results = self.parse_indexes_results_from_value(&value)?;
266        Ok(indexes_results)
267    }
268
269    /// List all [Indexes](Index) and returns values as instances of [Index].
270    ///
271    /// # Example
272    ///
273    /// ```
274    /// # use meilisearch_sdk::{client::*, indexes::*};
275    /// #
276    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
277    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
278    /// #
279    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
280    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
281    /// let mut query = IndexesQuery::new(&client);
282    /// query.with_limit(1);
283    ///
284    /// let indexes: IndexesResults = client.list_all_indexes_with(&query).await.unwrap();
285    ///
286    /// assert_eq!(indexes.limit, 1);
287    /// # });
288    /// ```
289    pub async fn list_all_indexes_with(
290        &self,
291        indexes_query: &IndexesQuery<'_, Http>,
292    ) -> Result<IndexesResults<Http>, Error> {
293        let value = self.list_all_indexes_raw_with(indexes_query).await?;
294        let indexes_results = self.parse_indexes_results_from_value(&value)?;
295
296        Ok(indexes_results)
297    }
298
299    /// List all [Indexes](Index) and returns as Json.
300    ///
301    /// # Example
302    ///
303    /// ```
304    /// # use meilisearch_sdk::{client::*, indexes::*};
305    /// #
306    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
307    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
308    /// #
309    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
310    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
311    /// let json_indexes = client.list_all_indexes_raw().await.unwrap();
312    ///
313    /// println!("{:?}", json_indexes);
314    /// # });
315    /// ```
316    pub async fn list_all_indexes_raw(&self) -> Result<Value, Error> {
317        let json_indexes = self
318            .http_client
319            .request::<(), (), Value>(
320                &format!("{}/indexes", self.host),
321                Method::Get { query: () },
322                200,
323            )
324            .await?;
325
326        Ok(json_indexes)
327    }
328
329    /// List all [Indexes](Index) with query parameters and returns as Json.
330    ///
331    /// # Example
332    ///
333    /// ```
334    /// # use meilisearch_sdk::{client::*, indexes::*};
335    /// #
336    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
337    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
338    /// #
339    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
340    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
341    /// let mut query = IndexesQuery::new(&client);
342    /// query.with_limit(1);
343    ///
344    /// let json_indexes = client.list_all_indexes_raw_with(&query).await.unwrap();
345    ///
346    /// println!("{:?}", json_indexes);
347    /// # });
348    /// ```
349    pub async fn list_all_indexes_raw_with(
350        &self,
351        indexes_query: &IndexesQuery<'_, Http>,
352    ) -> Result<Value, Error> {
353        let json_indexes = self
354            .http_client
355            .request::<&IndexesQuery<Http>, (), Value>(
356                &format!("{}/indexes", self.host),
357                Method::Get {
358                    query: indexes_query,
359                },
360                200,
361            )
362            .await?;
363
364        Ok(json_indexes)
365    }
366
367    /// Get an [Index], this index should already exist.
368    ///
369    /// # Example
370    ///
371    /// ```
372    /// # use meilisearch_sdk::{client::*, indexes::*};
373    /// #
374    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
375    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
376    /// #
377    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
378    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
379    /// # let index = client.create_index("get_index", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
380    /// let index = client.get_index("get_index").await.unwrap();
381    ///
382    /// assert_eq!(index.as_ref(), "get_index");
383    /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
384    /// # });
385    /// ```
386    pub async fn get_index(&self, uid: impl AsRef<str>) -> Result<Index<Http>, Error> {
387        let mut idx = self.index(uid.as_ref());
388        idx.fetch_info().await?;
389        Ok(idx)
390    }
391
392    /// Get a raw JSON [Index], this index should already exist.
393    ///
394    /// If you use it directly from an [Index], you can use the method [`Index::fetch_info`], which is the equivalent method from an index.
395    ///
396    /// # Example
397    ///
398    /// ```
399    /// # use meilisearch_sdk::{client::*, indexes::*};
400    /// #
401    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
402    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
403    /// #
404    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
405    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
406    /// # let index = client.create_index("get_raw_index", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
407    /// let raw_index = client.get_raw_index("get_raw_index").await.unwrap();
408    ///
409    /// assert_eq!(raw_index.get("uid").unwrap().as_str().unwrap(), "get_raw_index");
410    /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
411    /// # });
412    /// ```
413    pub async fn get_raw_index(&self, uid: impl AsRef<str>) -> Result<Value, Error> {
414        self.http_client
415            .request::<(), (), Value>(
416                &format!("{}/indexes/{}", self.host, uid.as_ref()),
417                Method::Get { query: () },
418                200,
419            )
420            .await
421    }
422
423    /// Create a corresponding object of an [Index] without any check or doing an HTTP call.
424    pub fn index(&self, uid: impl Into<String>) -> Index<Http> {
425        Index::new(uid, self.clone())
426    }
427
428    /// Create an [Index].
429    ///
430    /// The second parameter will be used as the primary key of the new index.
431    /// If it is not specified, Meilisearch will **try** to infer the primary key.
432    ///
433    /// # Example
434    ///
435    /// ```
436    /// # use meilisearch_sdk::{client::*, indexes::*};
437    /// #
438    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
439    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
440    /// #
441    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
442    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
443    /// // Create a new index called movies and access it
444    /// let task = client.create_index("create_index", None).await.unwrap();
445    ///
446    /// // Wait for the task to complete
447    /// let task = task.wait_for_completion(&client, None, None).await.unwrap();
448    ///
449    /// // Try to get the inner index if the task succeeded
450    /// let index = task.try_make_index(&client).unwrap();
451    ///
452    /// assert_eq!(index.as_ref(), "create_index");
453    /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
454    /// # });
455    /// ```
456    pub async fn create_index(
457        &self,
458        uid: impl AsRef<str>,
459        primary_key: Option<&str>,
460    ) -> Result<TaskInfo, Error> {
461        self.http_client
462            .request::<(), Value, TaskInfo>(
463                &format!("{}/indexes", self.host),
464                Method::Post {
465                    query: (),
466                    body: json!({
467                        "uid": uid.as_ref(),
468                        "primaryKey": primary_key,
469                    }),
470                },
471                202,
472            )
473            .await
474    }
475
476    /// Delete an index from its UID.
477    ///
478    /// To delete an [Index], use the [`Index::delete`] method.
479    pub async fn delete_index(&self, uid: impl AsRef<str>) -> Result<TaskInfo, Error> {
480        self.http_client
481            .request::<(), (), TaskInfo>(
482                &format!("{}/indexes/{}", self.host, uid.as_ref()),
483                Method::Delete { query: () },
484                202,
485            )
486            .await
487    }
488
489    /// Alias for [`Client::list_all_indexes`].
490    pub async fn get_indexes(&self) -> Result<IndexesResults<Http>, Error> {
491        self.list_all_indexes().await
492    }
493
494    /// Alias for [`Client::list_all_indexes_with`].
495    pub async fn get_indexes_with(
496        &self,
497        indexes_query: &IndexesQuery<'_, Http>,
498    ) -> Result<IndexesResults<Http>, Error> {
499        self.list_all_indexes_with(indexes_query).await
500    }
501
502    /// Alias for [`Client::list_all_indexes_raw`].
503    pub async fn get_indexes_raw(&self) -> Result<Value, Error> {
504        self.list_all_indexes_raw().await
505    }
506
507    /// Alias for [`Client::list_all_indexes_raw_with`].
508    pub async fn get_indexes_raw_with(
509        &self,
510        indexes_query: &IndexesQuery<'_, Http>,
511    ) -> Result<Value, Error> {
512        self.list_all_indexes_raw_with(indexes_query).await
513    }
514
515    /// Swaps a list of two [Indexes](Index).
516    ///
517    /// # Example
518    ///
519    /// ```
520    /// # use meilisearch_sdk::{client::*, indexes::*};
521    /// #
522    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
523    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
524    /// #
525    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
526    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
527    /// let task_index_1 = client.create_index("swap_index_1", None).await.unwrap();
528    /// let task_index_2 = client.create_index("swap_index_2", None).await.unwrap();
529    ///
530    /// // Wait for the task to complete
531    /// task_index_2.wait_for_completion(&client, None, None).await.unwrap();
532    ///
533    /// let task = client
534    ///     .swap_indexes([&SwapIndexes {
535    ///         indexes: (
536    ///             "swap_index_1".to_string(),
537    ///             "swap_index_2".to_string(),
538    ///         ),
539    ///         rename: None,
540    ///     }])
541    ///     .await
542    ///     .unwrap();
543    ///
544    /// client.index("swap_index_1").delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
545    /// client.index("swap_index_2").delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
546    /// # });
547    /// ```
548    pub async fn swap_indexes(
549        &self,
550        indexes: impl IntoIterator<Item = &SwapIndexes>,
551    ) -> Result<TaskInfo, Error> {
552        self.http_client
553            .request::<(), Vec<&SwapIndexes>, TaskInfo>(
554                &format!("{}/swap-indexes", self.host),
555                Method::Post {
556                    query: (),
557                    body: indexes.into_iter().collect(),
558                },
559                202,
560            )
561            .await
562    }
563
564    /// Get stats of all [Indexes](Index).
565    ///
566    /// # Example
567    ///
568    /// ```
569    /// # use meilisearch_sdk::{client::*, indexes::*};
570    /// #
571    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
572    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
573    /// #
574    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
575    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
576    /// let stats = client.get_stats().await.unwrap();
577    /// # });
578    /// ```
579    pub async fn get_stats(&self) -> Result<ClientStats, Error> {
580        self.http_client
581            .request::<(), (), ClientStats>(
582                &format!("{}/stats", self.host),
583                Method::Get { query: () },
584                200,
585            )
586            .await
587    }
588
589    /// Get health of Meilisearch server.
590    ///
591    /// # Example
592    ///
593    /// ```
594    /// # use meilisearch_sdk::{client::*, errors::*};
595    /// #
596    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
597    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
598    /// #
599    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
600    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
601    /// let health = client.health().await.unwrap();
602    ///
603    /// assert_eq!(health.status, "available");
604    /// # });
605    /// ```
606    pub async fn health(&self) -> Result<Health, Error> {
607        self.http_client
608            .request::<(), (), Health>(
609                &format!("{}/health", self.host),
610                Method::Get { query: () },
611                200,
612            )
613            .await
614    }
615
616    /// Get health of Meilisearch server.
617    ///
618    /// # Example
619    ///
620    /// ```
621    /// # use meilisearch_sdk::client::*;
622    /// #
623    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
624    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
625    /// #
626    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
627    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
628    /// let health = client.is_healthy().await;
629    ///
630    /// assert_eq!(health, true);
631    /// # });
632    /// ```
633    pub async fn is_healthy(&self) -> bool {
634        if let Ok(health) = self.health().await {
635            health.status.as_str() == "available"
636        } else {
637            false
638        }
639    }
640
641    /// Get the API [Keys](Key) from Meilisearch with parameters.
642    ///
643    /// See [`Client::create_key`], [`Client::get_key`], and the [meilisearch documentation](https://www.meilisearch.com/docs/reference/api/keys#get-all-keys).
644    ///
645    /// # Example
646    ///
647    /// ```
648    /// # use meilisearch_sdk::{client::*, errors::Error, key::KeysQuery};
649    /// #
650    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
651    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
652    /// #
653    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
654    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
655    /// let mut query = KeysQuery::new();
656    /// query.with_limit(1);
657    ///
658    /// let keys = client.get_keys_with(&query).await.unwrap();
659    ///
660    /// assert_eq!(keys.results.len(), 1);
661    /// # });
662    /// ```
663    pub async fn get_keys_with(&self, keys_query: &KeysQuery) -> Result<KeysResults, Error> {
664        let keys = self
665            .http_client
666            .request::<&KeysQuery, (), KeysResults>(
667                &format!("{}/keys", self.host),
668                Method::Get { query: keys_query },
669                200,
670            )
671            .await?;
672
673        Ok(keys)
674    }
675
676    /// Get the API [Keys](Key) from Meilisearch.
677    ///
678    /// See [`Client::create_key`], [`Client::get_key`], and the [meilisearch documentation](https://www.meilisearch.com/docs/reference/api/keys#get-all-keys).
679    ///
680    /// # Example
681    ///
682    /// ```
683    /// # use meilisearch_sdk::{client::*, errors::Error, key::KeyBuilder};
684    /// #
685    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
686    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
687    /// #
688    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
689    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
690    /// let keys = client.get_keys().await.unwrap();
691    ///
692    /// assert_eq!(keys.limit, 20);
693    /// # });
694    /// ```
695    pub async fn get_keys(&self) -> Result<KeysResults, Error> {
696        let keys = self
697            .http_client
698            .request::<(), (), KeysResults>(
699                &format!("{}/keys", self.host),
700                Method::Get { query: () },
701                200,
702            )
703            .await?;
704
705        Ok(keys)
706    }
707
708    /// Get one API [Key] from Meilisearch.
709    ///
710    /// See also [`Client::create_key`], [`Client::get_keys`], and the [meilisearch documentation](https://www.meilisearch.com/docs/reference/api/keys#get-one-key).
711    ///
712    /// # Example
713    ///
714    /// ```
715    /// # use meilisearch_sdk::{client::*, errors::Error, key::KeyBuilder};
716    /// #
717    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
718    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
719    /// #
720    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
721    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
722    /// # let key = client.get_keys().await.unwrap().results.into_iter()
723    /// #    .find(|k| k.name.as_ref().map_or(false, |name| name.starts_with("Default Search API Key")))
724    /// #    .expect("No default search key");
725    /// let key = client.get_key(key).await.expect("Invalid key");
726    ///
727    /// assert_eq!(key.name, Some("Default Search API Key".to_string()));
728    /// # });
729    /// ```
730    pub async fn get_key(&self, key: impl AsRef<str>) -> Result<Key, Error> {
731        self.http_client
732            .request::<(), (), Key>(
733                &format!("{}/keys/{}", self.host, key.as_ref()),
734                Method::Get { query: () },
735                200,
736            )
737            .await
738    }
739
740    /// Delete an API [Key] from Meilisearch.
741    ///
742    /// See also [`Client::create_key`], [`Client::update_key`], [`Client::get_key`], and the [meilisearch documentation](https://www.meilisearch.com/docs/reference/api/keys#delete-a-key).
743    ///
744    /// # Example
745    ///
746    /// ```
747    /// # use meilisearch_sdk::{client::*, errors::Error, key::KeyBuilder};
748    /// #
749    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
750    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
751    /// #
752    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
753    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
754    /// let key = KeyBuilder::new();
755    /// let key = client.create_key(key).await.unwrap();
756    /// let inner_key = key.key.clone();
757    ///
758    /// client.delete_key(key).await.unwrap();
759    ///
760    /// let keys = client.get_keys().await.unwrap();
761    ///
762    /// assert!(keys.results.iter().all(|key| key.key != inner_key));
763    /// # });
764    /// ```
765    pub async fn delete_key(&self, key: impl AsRef<str>) -> Result<(), Error> {
766        self.http_client
767            .request::<(), (), ()>(
768                &format!("{}/keys/{}", self.host, key.as_ref()),
769                Method::Delete { query: () },
770                204,
771            )
772            .await
773    }
774
775    /// Create an API [Key] in Meilisearch.
776    ///
777    /// See also [`Client::update_key`], [`Client::delete_key`], [`Client::get_key`], and the [meilisearch documentation](https://www.meilisearch.com/docs/reference/api/keys#create-a-key).
778    ///
779    /// # Example
780    ///
781    /// ```
782    /// # use meilisearch_sdk::{client::*, errors::Error, key::*};
783    /// #
784    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
785    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
786    /// #
787    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
788    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
789    /// let name = "create_key".to_string();
790    /// let mut key = KeyBuilder::new();
791    /// key.with_name(&name);
792    ///
793    /// let key = client.create_key(key).await.unwrap();
794    ///
795    /// assert_eq!(key.name, Some(name));
796    /// # client.delete_key(key).await.unwrap();
797    /// # });
798    /// ```
799    pub async fn create_key(&self, key: impl AsRef<KeyBuilder>) -> Result<Key, Error> {
800        self.http_client
801            .request::<(), &KeyBuilder, Key>(
802                &format!("{}/keys", self.host),
803                Method::Post {
804                    query: (),
805                    body: key.as_ref(),
806                },
807                201,
808            )
809            .await
810    }
811
812    /// Update an API [Key] in Meilisearch.
813    ///
814    /// See also [`Client::create_key`], [`Client::delete_key`], [`Client::get_key`], and the [meilisearch documentation](https://www.meilisearch.com/docs/reference/api/keys#update-a-key).
815    ///
816    /// # Example
817    ///
818    /// ```
819    /// # use meilisearch_sdk::{client::*, errors::Error, key::*};
820    /// #
821    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
822    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
823    /// #
824    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
825    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
826    /// let new_key = KeyBuilder::new();
827    /// let mut new_key = client.create_key(new_key).await.unwrap();
828    /// let mut key_update = KeyUpdater::new(new_key);
829    ///
830    /// let name = "my name".to_string();
831    /// key_update.with_name(&name);
832    ///
833    /// let key = client.update_key(key_update).await.unwrap();
834    ///
835    /// assert_eq!(key.name, Some(name));
836    /// # client.delete_key(key).await.unwrap();
837    /// # });
838    /// ```
839    pub async fn update_key(&self, key: impl AsRef<KeyUpdater>) -> Result<Key, Error> {
840        self.http_client
841            .request::<(), &KeyUpdater, Key>(
842                &format!("{}/keys/{}", self.host, key.as_ref().key),
843                Method::Patch {
844                    body: key.as_ref(),
845                    query: (),
846                },
847                200,
848            )
849            .await
850    }
851
852    /// Get version of the Meilisearch server.
853    ///
854    /// # Example
855    ///
856    /// ```
857    /// # use meilisearch_sdk::client::*;
858    /// #
859    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
860    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
861    /// #
862    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
863    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
864    /// let version = client.get_version().await.unwrap();
865    /// # });
866    /// ```
867    pub async fn get_version(&self) -> Result<Version, Error> {
868        self.http_client
869            .request::<(), (), Version>(
870                &format!("{}/version", self.host),
871                Method::Get { query: () },
872                200,
873            )
874            .await
875    }
876
877    /// Wait until Meilisearch processes a [Task], and get its status.
878    ///
879    /// `interval` = The frequency at which the server should be polled. **Default = 50ms**
880    ///
881    /// `timeout` = The maximum time to wait for processing to complete. **Default = 5000ms**
882    ///
883    /// If the waited time exceeds `timeout` then an [`Error::Timeout`] will be returned.
884    ///
885    /// See also [`Index::wait_for_task`, `Task::wait_for_completion`, `TaskInfo::wait_for_completion`].
886    ///
887    /// # Example
888    ///
889    /// ```
890    /// # use meilisearch_sdk::{client::*, indexes::*, tasks::*};
891    /// # use serde::{Serialize, Deserialize};
892    /// #
893    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
894    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
895    /// #
896    /// #
897    /// # #[derive(Debug, Serialize, Deserialize, PartialEq)]
898    /// # struct Document {
899    /// #    id: usize,
900    /// #    value: String,
901    /// #    kind: String,
902    /// # }
903    /// #
904    /// #
905    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
906    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
907    /// let movies = client.index("movies_client_wait_for_task");
908    ///
909    /// let task = movies.add_documents(&[
910    ///     Document { id: 0, kind: "title".into(), value: "The Social Network".to_string() },
911    ///     Document { id: 1, kind: "title".into(), value: "Harry Potter and the Sorcerer's Stone".to_string() },
912    /// ], None).await.unwrap();
913    ///
914    /// let status = client.wait_for_task(task, None, None).await.unwrap();
915    ///
916    /// assert!(matches!(status, Task::Succeeded { .. }));
917    /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
918    /// # });
919    /// ```
920    pub async fn wait_for_task(
921        &self,
922        task_id: impl AsRef<u32>,
923        interval: Option<Duration>,
924        timeout: Option<Duration>,
925    ) -> Result<Task, Error> {
926        let interval = interval.unwrap_or_else(|| Duration::from_millis(50));
927        let timeout = timeout.unwrap_or_else(|| Duration::from_millis(5000));
928
929        let mut elapsed_time = Duration::new(0, 0);
930        let mut task_result: Result<Task, Error>;
931
932        while timeout > elapsed_time {
933            task_result = self.get_task(&task_id).await;
934            match task_result {
935                Ok(status) => match status {
936                    Task::Failed { .. } | Task::Succeeded { .. } => {
937                        return self.get_task(task_id).await;
938                    }
939                    Task::Enqueued { .. } | Task::Processing { .. } => {
940                        elapsed_time += interval;
941                        self.sleep_backend().sleep(interval).await;
942                    }
943                },
944                Err(error) => return Err(error),
945            };
946        }
947
948        Err(Error::Timeout)
949    }
950
951    /// Get a task from the server given a task id.
952    ///
953    /// # Example
954    ///
955    /// ```
956    /// # use meilisearch_sdk::{client::*, tasks::*};
957    /// #
958    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
959    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
960    /// #
961    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
962    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
963    /// # let index = client.create_index("movies_get_task", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap();
964    /// let task = index.delete_all_documents().await.unwrap();
965    ///
966    /// let task = client.get_task(task).await.unwrap();
967    /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
968    /// # });
969    /// ```
970    pub async fn get_task(&self, task_id: impl AsRef<u32>) -> Result<Task, Error> {
971        self.http_client
972            .request::<(), (), Task>(
973                &format!("{}/tasks/{}", self.host, task_id.as_ref()),
974                Method::Get { query: () },
975                200,
976            )
977            .await
978    }
979
980    /// Get all tasks with query parameters from the server.
981    ///
982    /// # Example
983    ///
984    /// ```
985    /// # use meilisearch_sdk::{client::*, tasks::*};
986    /// #
987    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
988    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
989    /// #
990    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
991    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
992    /// let mut query = TasksSearchQuery::new(&client);
993    /// query.with_index_uids(["get_tasks_with"]);
994    ///
995    /// let tasks = client.get_tasks_with(&query).await.unwrap();
996    /// # });
997    /// ```
998    pub async fn get_tasks_with(
999        &self,
1000        tasks_query: &TasksSearchQuery<'_, Http>,
1001    ) -> Result<TasksResults, Error> {
1002        let tasks = self
1003            .http_client
1004            .request::<&TasksSearchQuery<Http>, (), TasksResults>(
1005                &format!("{}/tasks", self.host),
1006                Method::Get { query: tasks_query },
1007                200,
1008            )
1009            .await?;
1010
1011        Ok(tasks)
1012    }
1013
1014    /// Cancel tasks with filters [`TasksCancelQuery`].
1015    ///
1016    /// # Example
1017    ///
1018    /// ```
1019    /// # use meilisearch_sdk::{client::*, tasks::*};
1020    /// #
1021    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1022    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1023    /// #
1024    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1025    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1026    /// let mut query = TasksCancelQuery::new(&client);
1027    /// query.with_index_uids(["movies"]);
1028    ///
1029    /// let res = client.cancel_tasks_with(&query).await.unwrap();
1030    /// # });
1031    /// ```
1032    pub async fn cancel_tasks_with(
1033        &self,
1034        filters: &TasksCancelQuery<'_, Http>,
1035    ) -> Result<TaskInfo, Error> {
1036        let tasks = self
1037            .http_client
1038            .request::<&TasksCancelQuery<Http>, (), TaskInfo>(
1039                &format!("{}/tasks/cancel", self.host),
1040                Method::Post {
1041                    query: filters,
1042                    body: (),
1043                },
1044                200,
1045            )
1046            .await?;
1047
1048        Ok(tasks)
1049    }
1050
1051    /// Delete tasks with filters [`TasksDeleteQuery`].
1052    ///
1053    /// # Example
1054    ///
1055    /// ```
1056    /// # use meilisearch_sdk::{client::*, tasks::*};
1057    /// #
1058    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1059    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1060    /// #
1061    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1062    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1063    /// let mut query = TasksDeleteQuery::new(&client);
1064    /// query.with_index_uids(["movies"]);
1065    ///
1066    /// let res = client.delete_tasks_with(&query).await.unwrap();
1067    /// # });
1068    /// ```
1069    pub async fn delete_tasks_with(
1070        &self,
1071        filters: &TasksDeleteQuery<'_, Http>,
1072    ) -> Result<TaskInfo, Error> {
1073        let tasks = self
1074            .http_client
1075            .request::<&TasksDeleteQuery<Http>, (), TaskInfo>(
1076                &format!("{}/tasks", self.host),
1077                Method::Delete { query: filters },
1078                200,
1079            )
1080            .await?;
1081
1082        Ok(tasks)
1083    }
1084
1085    /// Get all tasks from the server.
1086    ///
1087    /// # Example
1088    ///
1089    /// ```
1090    /// # use meilisearch_sdk::{client::*, tasks::*};
1091    /// #
1092    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1093    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1094    /// #
1095    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1096    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1097    /// let tasks = client.get_tasks().await.unwrap();
1098    ///
1099    /// assert!(tasks.results.len() > 0);
1100    /// # });
1101    /// ```
1102    pub async fn get_tasks(&self) -> Result<TasksResults, Error> {
1103        let tasks = self
1104            .http_client
1105            .request::<(), (), TasksResults>(
1106                &format!("{}/tasks", self.host),
1107                Method::Get { query: () },
1108                200,
1109            )
1110            .await?;
1111
1112        Ok(tasks)
1113    }
1114
1115    /// List batches using the Batches API.
1116    ///
1117    /// See: https://www.meilisearch.com/docs/reference/api/batches
1118    ///
1119    /// # Example
1120    ///
1121    /// ```
1122    /// # use meilisearch_sdk::client::Client;
1123    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1124    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1125    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1126    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1127    /// let batches = client.get_batches().await.unwrap();
1128    /// # let _ = batches;
1129    /// # });
1130    /// ```
1131    pub async fn get_batches(&self) -> Result<crate::batches::BatchesResults, Error> {
1132        let res = self
1133            .http_client
1134            .request::<(), (), crate::batches::BatchesResults>(
1135                &format!("{}/batches", self.host),
1136                Method::Get { query: () },
1137                200,
1138            )
1139            .await?;
1140        Ok(res)
1141    }
1142
1143    /// List batches with pagination filters.
1144    ///
1145    /// # Example
1146    ///
1147    /// ```
1148    /// # use meilisearch_sdk::{client::Client, batches::BatchesQuery};
1149    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1150    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1151    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1152    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1153    /// let mut query = BatchesQuery::new(&client);
1154    /// query.with_limit(1);
1155    /// let batches = client.get_batches_with(&query).await.unwrap();
1156    /// # let _ = batches;
1157    /// # });
1158    /// ```
1159    pub async fn get_batches_with(
1160        &self,
1161        query: &crate::batches::BatchesQuery<'_, Http>,
1162    ) -> Result<crate::batches::BatchesResults, Error> {
1163        let res = self
1164            .http_client
1165            .request::<&crate::batches::BatchesQuery<'_, Http>, (), crate::batches::BatchesResults>(
1166                &format!("{}/batches", self.host),
1167                Method::Get { query },
1168                200,
1169            )
1170            .await?;
1171        Ok(res)
1172    }
1173
1174    /// Get a single batch by its uid.
1175    ///
1176    /// # Example
1177    ///
1178    /// ```
1179    /// # use meilisearch_sdk::client::Client;
1180    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1181    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1182    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1183    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1184    /// let uid: u32 = 42;
1185    /// let batch = client.get_batch(uid).await.unwrap();
1186    /// # let _ = batch;
1187    /// # });
1188    /// ```
1189    pub async fn get_batch(&self, uid: u32) -> Result<crate::batches::Batch, Error> {
1190        let res = self
1191            .http_client
1192            .request::<(), (), crate::batches::Batch>(
1193                &format!("{}/batches/{}", self.host, uid),
1194                Method::Get { query: () },
1195                200,
1196            )
1197            .await?;
1198        Ok(res)
1199    }
1200
1201    /// Generates a new tenant token.
1202    ///
1203    /// # Example
1204    ///
1205    /// ```
1206    /// # use meilisearch_sdk::client::Client;
1207    /// #
1208    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1209    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1210    /// #
1211    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1212    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1213    /// let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string();
1214    /// let token = client.generate_tenant_token(api_key_uid, serde_json::json!(["*"]), None, None).unwrap();
1215    ///
1216    /// let client = Client::new(MEILISEARCH_URL, Some(token)).unwrap();
1217    /// # });
1218    /// ```
1219    #[cfg(not(target_arch = "wasm32"))]
1220    pub fn generate_tenant_token(
1221        &self,
1222        api_key_uid: String,
1223        search_rules: Value,
1224        api_key: Option<&str>,
1225        expires_at: Option<OffsetDateTime>,
1226    ) -> Result<String, Error> {
1227        let api_key = match self.get_api_key() {
1228            Some(key) => api_key.unwrap_or(key),
1229            None => {
1230                return Err(Error::CantUseWithoutApiKey(
1231                    "generate_tenant_token".to_string(),
1232                ))
1233            }
1234        };
1235
1236        crate::tenant_tokens::generate_tenant_token(api_key_uid, search_rules, api_key, expires_at)
1237    }
1238
1239    /// Get the current network state (/network)
1240    pub async fn get_network_state(&self) -> Result<NetworkState, Error> {
1241        self.http_client
1242            .request::<(), (), NetworkState>(
1243                &format!("{}/network", self.host),
1244                Method::Get { query: () },
1245                200,
1246            )
1247            .await
1248    }
1249
1250    /// Partially update the network state (/network)
1251    pub async fn update_network_state(&self, body: &NetworkUpdate) -> Result<NetworkState, Error> {
1252        self.http_client
1253            .request::<(), &NetworkUpdate, NetworkState>(
1254                &format!("{}/network", self.host),
1255                Method::Patch { query: (), body },
1256                200,
1257            )
1258            .await
1259    }
1260
1261    /// Convenience: set sharding=true/false
1262    pub async fn set_sharding(&self, enabled: bool) -> Result<NetworkState, Error> {
1263        let update = NetworkUpdate {
1264            sharding: Some(enabled),
1265            ..NetworkUpdate::default()
1266        };
1267        self.update_network_state(&update).await
1268    }
1269
1270    /// Convenience: set self to a remote name
1271    pub async fn set_self_remote(&self, name: &str) -> Result<NetworkState, Error> {
1272        let update = NetworkUpdate {
1273            self_name: Some(name.to_string()),
1274            ..NetworkUpdate::default()
1275        };
1276        self.update_network_state(&update).await
1277    }
1278
1279    /// List all webhooks registered on the Meilisearch instance.
1280    ///
1281    ///
1282    /// # Example
1283    ///
1284    /// ```
1285    /// # use meilisearch_sdk::{client::*, webhooks::*};
1286    /// #
1287    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1288    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1289    /// #
1290    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1291    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1292    /// if let Ok(webhooks) = client.get_webhooks().await {
1293    ///     println!("{}", webhooks.results.len());
1294    /// }
1295    /// # });
1296    /// ```
1297    pub async fn get_webhooks(&self) -> Result<WebhookList, Error> {
1298        self.http_client
1299            .request::<(), (), WebhookList>(
1300                &format!("{}/webhooks", self.host),
1301                Method::Get { query: () },
1302                200,
1303            )
1304            .await
1305    }
1306
1307    /// Retrieve a single webhook by its UUID.
1308    ///
1309    ///
1310    /// # Example
1311    ///
1312    /// ```
1313    /// # use meilisearch_sdk::{client::*, webhooks::*};
1314    /// #
1315    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1316    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1317    /// #
1318    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1319    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1320    /// # if let Ok(created) = client.create_webhook(&WebhookCreate::new("https://example.com")).await {
1321    /// if let Ok(webhook) = client.get_webhook(&created.uuid.to_string()).await {
1322    ///     println!("{}", webhook.webhook.url);
1323    /// #   let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1324    /// }
1325    /// # }
1326    /// # });
1327    /// ```
1328    pub async fn get_webhook(&self, uuid: impl AsRef<str>) -> Result<WebhookInfo, Error> {
1329        self.http_client
1330            .request::<(), (), WebhookInfo>(
1331                &format!("{}/webhooks/{}", self.host, uuid.as_ref()),
1332                Method::Get { query: () },
1333                200,
1334            )
1335            .await
1336    }
1337
1338    /// Create a new webhook.
1339    ///
1340    ///
1341    /// # Example
1342    ///
1343    /// ```
1344    /// # use meilisearch_sdk::{client::*, webhooks::*};
1345    /// #
1346    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1347    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1348    /// #
1349    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1350    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1351    /// if let Ok(webhook) = client
1352    ///     .create_webhook(&WebhookCreate::new("https://example.com/webhook"))
1353    ///     .await
1354    /// {
1355    ///     assert!(webhook.is_editable);
1356    /// #   let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1357    /// }
1358    /// # });
1359    /// ```
1360    pub async fn create_webhook(&self, webhook: &WebhookCreate) -> Result<WebhookInfo, Error> {
1361        self.http_client
1362            .request::<(), &WebhookCreate, WebhookInfo>(
1363                &format!("{}/webhooks", self.host),
1364                Method::Post {
1365                    query: (),
1366                    body: webhook,
1367                },
1368                201,
1369            )
1370            .await
1371    }
1372
1373    /// Update an existing webhook.
1374    ///
1375    ///
1376    /// # Example
1377    ///
1378    /// ```
1379    /// # use meilisearch_sdk::{client::*, webhooks::*};
1380    /// #
1381    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1382    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1383    /// #
1384    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1385    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1386    /// if let Ok(webhook) = client.create_webhook(&WebhookCreate::new("https://example.com")).await {
1387    ///     let mut update = WebhookUpdate::new();
1388    ///     update.set_header("authorization", "SECURITY_KEY");
1389    ///     let _ = client
1390    ///         .update_webhook(&webhook.uuid.to_string(), &update)
1391    ///         .await;
1392    /// #   let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1393    /// }
1394    /// # });
1395    /// ```
1396    pub async fn update_webhook(
1397        &self,
1398        uuid: impl AsRef<str>,
1399        webhook: &WebhookUpdate,
1400    ) -> Result<WebhookInfo, Error> {
1401        self.http_client
1402            .request::<(), &WebhookUpdate, WebhookInfo>(
1403                &format!("{}/webhooks/{}", self.host, uuid.as_ref()),
1404                Method::Patch {
1405                    query: (),
1406                    body: webhook,
1407                },
1408                200,
1409            )
1410            .await
1411    }
1412
1413    /// Delete a webhook by its UUID.
1414    ///
1415    ///
1416    /// # Example
1417    ///
1418    /// ```
1419    /// # use meilisearch_sdk::{client::*, webhooks::*};
1420    /// #
1421    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1422    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1423    /// #
1424    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1425    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1426    /// if let Ok(webhook) = client.create_webhook(&WebhookCreate::new("https://example.com")).await {
1427    ///     let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1428    /// }
1429    /// # });
1430    /// ```
1431    pub async fn delete_webhook(&self, uuid: impl AsRef<str>) -> Result<(), Error> {
1432        self.http_client
1433            .request::<(), (), ()>(
1434                &format!("{}/webhooks/{}", self.host, uuid.as_ref()),
1435                Method::Delete { query: () },
1436                204,
1437            )
1438            .await
1439    }
1440
1441    fn sleep_backend(&self) -> SleepBackend {
1442        SleepBackend::infer(self.http_client.is_tokio())
1443    }
1444}
1445
1446#[derive(Debug, Clone, Deserialize)]
1447#[serde(rename_all = "camelCase")]
1448pub struct ClientStats {
1449    /// Storage space claimed by Meilisearch and LMDB in bytes
1450    pub database_size: usize,
1451
1452    /// Storage space used by the database in bytes, excluding unused space claimed by LMDB
1453    pub used_database_size: usize,
1454
1455    /// When the last update was made to the database in the `RFC 3339` format
1456    #[serde(with = "time::serde::rfc3339::option")]
1457    pub last_update: Option<OffsetDateTime>,
1458
1459    /// The statistics for each index found in the database
1460    pub indexes: HashMap<String, IndexStats>,
1461}
1462
1463/// Health of the Meilisearch server.
1464///
1465/// # Example
1466///
1467/// ```
1468/// # use meilisearch_sdk::{client::*, indexes::*, errors::Error};
1469/// Health {
1470///     status: "available".to_string(),
1471/// };
1472/// ```
1473#[derive(Debug, Clone, Deserialize)]
1474pub struct Health {
1475    pub status: String,
1476}
1477
1478/// Version of a Meilisearch server.
1479///
1480/// # Example
1481///
1482/// ```
1483/// # use meilisearch_sdk::{client::*, indexes::*, errors::Error};
1484/// Version {
1485///     commit_sha: "b46889b5f0f2f8b91438a08a358ba8f05fc09fc1".to_string(),
1486///     commit_date: "2019-11-15T09:51:54.278247+00:00".to_string(),
1487///     pkg_version: "0.1.1".to_string(),
1488/// };
1489/// ```
1490#[derive(Debug, Clone, Deserialize)]
1491#[serde(rename_all = "camelCase")]
1492pub struct Version {
1493    pub commit_sha: String,
1494    pub commit_date: String,
1495    pub pkg_version: String,
1496}
1497
1498#[cfg(test)]
1499mod tests {
1500    use super::*;
1501    use mockito::Matcher;
1502
1503    #[tokio::test]
1504    async fn test_network_update_and_deserialize_remotes() {
1505        let mut s = mockito::Server::new_async().await;
1506        let base = s.url();
1507
1508        let response_body = serde_json::json!({
1509            "remotes": {
1510                "ms-00": {
1511                    "url": "http://ms-00",
1512                    "searchApiKey": "SEARCH",
1513                    "writeApiKey": "WRITE"
1514                }
1515            },
1516            "self": "ms-00",
1517            "sharding": true
1518        })
1519        .to_string();
1520
1521        let _m = s
1522            .mock("PATCH", "/network")
1523            .match_body(Matcher::Regex(
1524                r#"\{.*"sharding"\s*:\s*true.*\}"#.to_string(),
1525            ))
1526            .with_status(200)
1527            .with_header("content-type", "application/json")
1528            .with_body(response_body)
1529            .create_async()
1530            .await;
1531
1532        let client = Client::new(base, None::<String>).unwrap();
1533        let updated = client
1534            .set_sharding(true)
1535            .await
1536            .expect("update_network_state failed");
1537        assert_eq!(updated.sharding, Some(true));
1538        let remotes = updated.remotes.expect("remotes should be present");
1539        let ms00 = remotes.get("ms-00").expect("ms-00 should exist");
1540        assert_eq!(ms00.write_api_key.as_deref(), Some("WRITE"));
1541    }
1542
1543    use big_s::S;
1544    use time::OffsetDateTime;
1545
1546    use meilisearch_test_macro::meilisearch_test;
1547
1548    use crate::{client::*, key::Action, reqwest::qualified_version};
1549
1550    #[derive(Debug, Serialize, Deserialize, PartialEq)]
1551    struct Document {
1552        id: String,
1553    }
1554
1555    #[meilisearch_test]
1556    async fn test_swapping_two_indexes(client: Client) {
1557        let index_1 = client.index("test_swapping_two_indexes_1");
1558        let index_2 = client.index("test_swapping_two_indexes_2");
1559
1560        let t0 = index_1
1561            .add_documents(
1562                &[Document {
1563                    id: "1".to_string(),
1564                }],
1565                None,
1566            )
1567            .await
1568            .unwrap();
1569
1570        index_2
1571            .add_documents(
1572                &[Document {
1573                    id: "2".to_string(),
1574                }],
1575                None,
1576            )
1577            .await
1578            .unwrap();
1579
1580        t0.wait_for_completion(&client, None, None).await.unwrap();
1581
1582        let task = client
1583            .swap_indexes([&SwapIndexes {
1584                indexes: (
1585                    "test_swapping_two_indexes_1".to_string(),
1586                    "test_swapping_two_indexes_2".to_string(),
1587                ),
1588                rename: None,
1589            }])
1590            .await
1591            .unwrap();
1592        task.wait_for_completion(&client, None, None).await.unwrap();
1593
1594        let document = index_1.get_document("2").await.unwrap();
1595
1596        assert_eq!(
1597            Document {
1598                id: "2".to_string()
1599            },
1600            document
1601        );
1602    }
1603
1604    #[meilisearch_test]
1605    async fn test_methods_has_qualified_version_as_header() {
1606        let mut s = mockito::Server::new_async().await;
1607        let mock_server_url = s.url();
1608        let path = "/hello";
1609        let address = &format!("{mock_server_url}{path}");
1610        let user_agent = &*qualified_version();
1611        let client = Client::new(mock_server_url, None::<String>).unwrap();
1612
1613        let assertions = vec![
1614            (
1615                s.mock("GET", path)
1616                    .match_header("User-Agent", user_agent)
1617                    .create_async()
1618                    .await,
1619                client
1620                    .http_client
1621                    .request::<(), (), ()>(address, Method::Get { query: () }, 200),
1622            ),
1623            (
1624                s.mock("POST", path)
1625                    .match_header("User-Agent", user_agent)
1626                    .create_async()
1627                    .await,
1628                client.http_client.request::<(), (), ()>(
1629                    address,
1630                    Method::Post {
1631                        query: (),
1632                        body: {},
1633                    },
1634                    200,
1635                ),
1636            ),
1637            (
1638                s.mock("DELETE", path)
1639                    .match_header("User-Agent", user_agent)
1640                    .create_async()
1641                    .await,
1642                client.http_client.request::<(), (), ()>(
1643                    address,
1644                    Method::Delete { query: () },
1645                    200,
1646                ),
1647            ),
1648            (
1649                s.mock("PUT", path)
1650                    .match_header("User-Agent", user_agent)
1651                    .create_async()
1652                    .await,
1653                client.http_client.request::<(), (), ()>(
1654                    address,
1655                    Method::Put {
1656                        query: (),
1657                        body: (),
1658                    },
1659                    200,
1660                ),
1661            ),
1662            (
1663                s.mock("PATCH", path)
1664                    .match_header("User-Agent", user_agent)
1665                    .create_async()
1666                    .await,
1667                client.http_client.request::<(), (), ()>(
1668                    address,
1669                    Method::Patch {
1670                        query: (),
1671                        body: (),
1672                    },
1673                    200,
1674                ),
1675            ),
1676        ];
1677
1678        for (m, req) in assertions {
1679            let _ = req.await;
1680
1681            m.assert_async().await;
1682        }
1683    }
1684
1685    #[meilisearch_test]
1686    async fn test_get_tasks(client: Client) {
1687        let tasks = client.get_tasks().await.unwrap();
1688        assert_eq!(tasks.limit, 20);
1689    }
1690
1691    #[meilisearch_test]
1692    async fn test_rename_index_via_swap(client: Client, name: String) -> Result<(), Error> {
1693        let from = format!("{name}_from");
1694        let to = format!("{name}_to");
1695
1696        client
1697            .create_index(&from, None)
1698            .await?
1699            .wait_for_completion(&client, None, None)
1700            .await?;
1701
1702        let task = client
1703            .swap_indexes([&SwapIndexes {
1704                indexes: (from.clone(), to.clone()),
1705                rename: Some(true),
1706            }])
1707            .await?;
1708        task.wait_for_completion(&client, None, None).await?;
1709
1710        let new_index = client.get_index(&to).await?;
1711        assert_eq!(new_index.uid, to);
1712        // Optional: old uid should no longer resolve
1713        assert!(client.get_raw_index(&from).await.is_err());
1714
1715        new_index
1716            .delete()
1717            .await?
1718            .wait_for_completion(&client, None, None)
1719            .await?;
1720
1721        Ok(())
1722    }
1723
1724    #[meilisearch_test]
1725    async fn test_get_tasks_with_params(client: Client) {
1726        let query = TasksSearchQuery::new(&client);
1727        let tasks = client.get_tasks_with(&query).await.unwrap();
1728
1729        assert_eq!(tasks.limit, 20);
1730    }
1731
1732    #[meilisearch_test]
1733    async fn test_get_keys(client: Client) {
1734        let keys = client.get_keys().await.unwrap();
1735
1736        assert!(keys.results.len() >= 2);
1737    }
1738
1739    #[meilisearch_test]
1740    async fn test_delete_key(client: Client, name: String) {
1741        let mut key = KeyBuilder::new();
1742        key.with_name(&name);
1743        let key = client.create_key(key).await.unwrap();
1744
1745        client.delete_key(&key).await.unwrap();
1746        let keys = KeysQuery::new()
1747            .with_limit(10000)
1748            .execute(&client)
1749            .await
1750            .unwrap();
1751
1752        assert!(keys.results.iter().all(|k| k.key != key.key));
1753    }
1754
1755    #[meilisearch_test]
1756    async fn test_error_delete_key(client: Client, name: String) {
1757        // ==> accessing a key that does not exist
1758        let error = client.delete_key("invalid_key").await.unwrap_err();
1759        insta::assert_snapshot!(error, @"Meilisearch invalid_request: api_key_not_found: API key `invalid_key` not found.. https://docs.meilisearch.com/errors#api_key_not_found");
1760
1761        // ==> executing the action without enough right
1762        let mut key = KeyBuilder::new();
1763
1764        key.with_name(&name);
1765        let key = client.create_key(key).await.unwrap();
1766        let master_key = client.api_key.clone();
1767
1768        // create a new client with no right
1769        let client = Client::new(client.host, Some(key.key.clone())).unwrap();
1770        // with a wrong key
1771        let error = client.delete_key("invalid_key").await.unwrap_err();
1772        insta::assert_snapshot!(error, @"Meilisearch auth: invalid_api_key: The provided API key is invalid.. https://docs.meilisearch.com/errors#invalid_api_key");
1773        assert!(matches!(
1774            error,
1775            Error::Meilisearch(MeilisearchError {
1776                error_code: ErrorCode::InvalidApiKey,
1777                error_type: ErrorType::Auth,
1778                ..
1779            })
1780        ));
1781        // with a good key
1782        let error = client.delete_key(&key.key).await.unwrap_err();
1783        insta::assert_snapshot!(error, @"Meilisearch auth: invalid_api_key: The provided API key is invalid.. https://docs.meilisearch.com/errors#invalid_api_key");
1784        assert!(matches!(
1785            error,
1786            Error::Meilisearch(MeilisearchError {
1787                error_code: ErrorCode::InvalidApiKey,
1788                error_type: ErrorType::Auth,
1789                ..
1790            })
1791        ));
1792
1793        // cleanup
1794        let client = Client::new(client.host, master_key).unwrap();
1795        client.delete_key(key).await.unwrap();
1796    }
1797
1798    #[meilisearch_test]
1799    async fn test_create_key(client: Client, name: String) {
1800        let expires_at = OffsetDateTime::now_utc() + time::Duration::HOUR;
1801        let mut key = KeyBuilder::new();
1802        key.with_action(Action::DocumentsAdd)
1803            .with_name(&name)
1804            .with_expires_at(expires_at)
1805            .with_description("a description")
1806            .with_index("*");
1807        let key = client.create_key(key).await.unwrap();
1808
1809        assert_eq!(key.actions, vec![Action::DocumentsAdd]);
1810        assert_eq!(&key.name, &Some(name));
1811        // We can't compare the two timestamps directly because of some nanoseconds imprecision with the floats
1812        assert_eq!(
1813            key.expires_at.unwrap().unix_timestamp(),
1814            expires_at.unix_timestamp()
1815        );
1816        assert_eq!(key.indexes, vec![S("*")]);
1817
1818        client.delete_key(key).await.unwrap();
1819    }
1820
1821    #[meilisearch_test]
1822    async fn test_error_create_key(client: Client, name: String) {
1823        // ==> Invalid index name
1824        /* TODO: uncomment once meilisearch fix this bug: https://github.com/meilisearch/meilisearch/issues/2158
1825        let mut key = KeyBuilder::new();
1826        key.with_index("invalid index # / \\name with spaces");
1827        let error = client.create_key(key).await.unwrap_err();
1828
1829        assert!(matches!(
1830            error,
1831            Error::MeilisearchError {
1832                error_code: ErrorCode::InvalidApiKeyIndexes,
1833                error_type: ErrorType::InvalidRequest,
1834                ..
1835            }
1836        ));
1837        */
1838        // ==> executing the action without enough right
1839        let mut no_right_key = KeyBuilder::new();
1840        no_right_key.with_name(format!("{name}_1"));
1841        let no_right_key = client.create_key(no_right_key).await.unwrap();
1842
1843        // backup the master key for cleanup at the end of the test
1844        let master_client = client.clone();
1845        let client = Client::new(&master_client.host, Some(no_right_key.key.clone())).unwrap();
1846
1847        let mut key = KeyBuilder::new();
1848        key.with_name(format!("{name}_2"));
1849        let error = client.create_key(key).await.unwrap_err();
1850
1851        assert!(matches!(
1852            error,
1853            Error::Meilisearch(MeilisearchError {
1854                error_code: ErrorCode::InvalidApiKey,
1855                error_type: ErrorType::Auth,
1856                ..
1857            })
1858        ));
1859
1860        // cleanup
1861        master_client
1862            .delete_key(client.api_key.unwrap())
1863            .await
1864            .unwrap();
1865    }
1866
1867    #[meilisearch_test]
1868    async fn test_update_key(client: Client, description: String) {
1869        let mut key = KeyBuilder::new();
1870        key.with_name("test_update_key");
1871        let mut key = client.create_key(key).await.unwrap();
1872
1873        let name = S("new name");
1874        key.with_description(&description);
1875        key.with_name(&name);
1876
1877        let key = key.update(&client).await.unwrap();
1878
1879        assert_eq!(key.description, Some(description));
1880        assert_eq!(key.name, Some(name));
1881
1882        client.delete_key(key).await.unwrap();
1883    }
1884
1885    #[meilisearch_test]
1886    async fn test_get_index(client: Client, index_uid: String) -> Result<(), Error> {
1887        let task = client.create_index(&index_uid, None).await?;
1888        let index = client
1889            .wait_for_task(task, None, None)
1890            .await?
1891            .try_make_index(&client)
1892            .unwrap();
1893
1894        assert_eq!(index.uid, index_uid);
1895        index
1896            .delete()
1897            .await?
1898            .wait_for_completion(&client, None, None)
1899            .await?;
1900        Ok(())
1901    }
1902
1903    #[meilisearch_test]
1904    async fn test_error_create_index(client: Client, index: Index) -> Result<(), Error> {
1905        let error = client
1906            .create_index("Wrong index name", None)
1907            .await
1908            .unwrap_err();
1909
1910        assert!(matches!(
1911            error,
1912            Error::Meilisearch(MeilisearchError {
1913                error_code: ErrorCode::InvalidIndexUid,
1914                error_type: ErrorType::InvalidRequest,
1915                ..
1916            })
1917        ));
1918
1919        // we try to create an index with the same uid of an already existing index
1920        let error = client
1921            .create_index(&*index.uid, None)
1922            .await?
1923            .wait_for_completion(&client, None, None)
1924            .await?
1925            .unwrap_failure();
1926
1927        assert!(matches!(
1928            error,
1929            MeilisearchError {
1930                error_code: ErrorCode::IndexAlreadyExists,
1931                error_type: ErrorType::InvalidRequest,
1932                ..
1933            }
1934        ));
1935        Ok(())
1936    }
1937
1938    #[meilisearch_test]
1939    async fn test_list_all_indexes(client: Client) {
1940        let all_indexes = client.list_all_indexes().await.unwrap();
1941
1942        assert_eq!(all_indexes.limit, 20);
1943        assert_eq!(all_indexes.offset, 0);
1944    }
1945
1946    #[meilisearch_test]
1947    async fn test_list_all_indexes_with_params(client: Client) {
1948        let mut query = IndexesQuery::new(&client);
1949        query.with_limit(1);
1950        let all_indexes = client.list_all_indexes_with(&query).await.unwrap();
1951
1952        assert_eq!(all_indexes.limit, 1);
1953        assert_eq!(all_indexes.offset, 0);
1954    }
1955
1956    #[meilisearch_test]
1957    async fn test_list_all_indexes_raw(client: Client) {
1958        let all_indexes_raw = client.list_all_indexes_raw().await.unwrap();
1959
1960        assert_eq!(all_indexes_raw["limit"], json!(20));
1961        assert_eq!(all_indexes_raw["offset"], json!(0));
1962    }
1963
1964    #[meilisearch_test]
1965    async fn test_list_all_indexes_raw_with_params(client: Client) {
1966        let mut query = IndexesQuery::new(&client);
1967        query.with_limit(1);
1968        let all_indexes_raw = client.list_all_indexes_raw_with(&query).await.unwrap();
1969
1970        assert_eq!(all_indexes_raw["limit"], json!(1));
1971        assert_eq!(all_indexes_raw["offset"], json!(0));
1972    }
1973
1974    #[meilisearch_test]
1975    async fn test_get_primary_key_is_none(mut index: Index) {
1976        let primary_key = index.get_primary_key().await;
1977
1978        assert!(primary_key.is_ok());
1979        assert!(primary_key.unwrap().is_none());
1980    }
1981
1982    #[meilisearch_test]
1983    async fn test_get_primary_key(client: Client, index_uid: String) -> Result<(), Error> {
1984        let mut index = client
1985            .create_index(index_uid, Some("primary_key"))
1986            .await?
1987            .wait_for_completion(&client, None, None)
1988            .await?
1989            .try_make_index(&client)
1990            .unwrap();
1991
1992        let primary_key = index.get_primary_key().await;
1993        assert!(primary_key.is_ok());
1994        assert_eq!(primary_key?.unwrap(), "primary_key");
1995
1996        index
1997            .delete()
1998            .await?
1999            .wait_for_completion(&client, None, None)
2000            .await?;
2001
2002        Ok(())
2003    }
2004}