Skip to main content

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    ///
1241    /// Includes the `leader` and `version` fields introduced in Meilisearch v1.30.
1242    pub async fn get_network_state(&self) -> Result<NetworkState, Error> {
1243        self.http_client
1244            .request::<(), (), NetworkState>(
1245                &format!("{}/network", self.host),
1246                Method::Get { query: () },
1247                200,
1248            )
1249            .await
1250    }
1251
1252    /// Partially update the network state (/network).
1253    ///
1254    /// Returns a `networkTopologyChange` task that can be awaited for completion.
1255    pub async fn update_network_state(&self, body: &NetworkUpdate) -> Result<TaskInfo, Error> {
1256        self.http_client
1257            .request::<(), &NetworkUpdate, TaskInfo>(
1258                &format!("{}/network", self.host),
1259                Method::Patch { query: (), body },
1260                202,
1261            )
1262            .await
1263    }
1264
1265    /// Convenience: set self to a remote name.
1266    pub async fn set_self_remote(&self, name: &str) -> Result<TaskInfo, Error> {
1267        let update = NetworkUpdate {
1268            self_name: Some(name.to_string()),
1269            ..NetworkUpdate::default()
1270        };
1271        self.update_network_state(&update).await
1272    }
1273
1274    /// Convenience: set the leader value in the network configuration.
1275    ///
1276    /// This is required when enabling sharding in Meilisearch v1.30+.
1277    pub async fn set_network_leader(&self, leader: &str) -> Result<TaskInfo, Error> {
1278        let update = NetworkUpdate {
1279            leader: Some(leader.to_string()),
1280            ..NetworkUpdate::default()
1281        };
1282        self.update_network_state(&update).await
1283    }
1284
1285    /// List all webhooks registered on the Meilisearch instance.
1286    ///
1287    ///
1288    /// # Example
1289    ///
1290    /// ```
1291    /// # use meilisearch_sdk::{client::*, webhooks::*};
1292    /// #
1293    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1294    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1295    /// #
1296    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1297    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1298    /// if let Ok(webhooks) = client.get_webhooks().await {
1299    ///     println!("{}", webhooks.results.len());
1300    /// }
1301    /// # });
1302    /// ```
1303    pub async fn get_webhooks(&self) -> Result<WebhookList, Error> {
1304        self.http_client
1305            .request::<(), (), WebhookList>(
1306                &format!("{}/webhooks", self.host),
1307                Method::Get { query: () },
1308                200,
1309            )
1310            .await
1311    }
1312
1313    /// Retrieve a single webhook by its UUID.
1314    ///
1315    ///
1316    /// # Example
1317    ///
1318    /// ```
1319    /// # use meilisearch_sdk::{client::*, webhooks::*};
1320    /// #
1321    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1322    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1323    /// #
1324    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1325    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1326    /// # if let Ok(created) = client.create_webhook(&WebhookCreate::new("https://example.com")).await {
1327    /// if let Ok(webhook) = client.get_webhook(&created.uuid.to_string()).await {
1328    ///     println!("{}", webhook.webhook.url);
1329    /// #   let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1330    /// }
1331    /// # }
1332    /// # });
1333    /// ```
1334    pub async fn get_webhook(&self, uuid: impl AsRef<str>) -> Result<WebhookInfo, Error> {
1335        self.http_client
1336            .request::<(), (), WebhookInfo>(
1337                &format!("{}/webhooks/{}", self.host, uuid.as_ref()),
1338                Method::Get { query: () },
1339                200,
1340            )
1341            .await
1342    }
1343
1344    /// Create a new webhook.
1345    ///
1346    ///
1347    /// # Example
1348    ///
1349    /// ```
1350    /// # use meilisearch_sdk::{client::*, webhooks::*};
1351    /// #
1352    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1353    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1354    /// #
1355    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1356    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1357    /// if let Ok(webhook) = client
1358    ///     .create_webhook(&WebhookCreate::new("https://example.com/webhook"))
1359    ///     .await
1360    /// {
1361    ///     assert!(webhook.is_editable);
1362    /// #   let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1363    /// }
1364    /// # });
1365    /// ```
1366    pub async fn create_webhook(&self, webhook: &WebhookCreate) -> Result<WebhookInfo, Error> {
1367        self.http_client
1368            .request::<(), &WebhookCreate, WebhookInfo>(
1369                &format!("{}/webhooks", self.host),
1370                Method::Post {
1371                    query: (),
1372                    body: webhook,
1373                },
1374                201,
1375            )
1376            .await
1377    }
1378
1379    /// Update an existing webhook.
1380    ///
1381    ///
1382    /// # Example
1383    ///
1384    /// ```
1385    /// # use meilisearch_sdk::{client::*, webhooks::*};
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    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1391    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1392    /// if let Ok(webhook) = client.create_webhook(&WebhookCreate::new("https://example.com")).await {
1393    ///     let mut update = WebhookUpdate::new();
1394    ///     update.set_header("authorization", "SECURITY_KEY");
1395    ///     let _ = client
1396    ///         .update_webhook(&webhook.uuid.to_string(), &update)
1397    ///         .await;
1398    /// #   let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1399    /// }
1400    /// # });
1401    /// ```
1402    pub async fn update_webhook(
1403        &self,
1404        uuid: impl AsRef<str>,
1405        webhook: &WebhookUpdate,
1406    ) -> Result<WebhookInfo, Error> {
1407        self.http_client
1408            .request::<(), &WebhookUpdate, WebhookInfo>(
1409                &format!("{}/webhooks/{}", self.host, uuid.as_ref()),
1410                Method::Patch {
1411                    query: (),
1412                    body: webhook,
1413                },
1414                200,
1415            )
1416            .await
1417    }
1418
1419    /// Delete a webhook by its UUID.
1420    ///
1421    ///
1422    /// # Example
1423    ///
1424    /// ```
1425    /// # use meilisearch_sdk::{client::*, webhooks::*};
1426    /// #
1427    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1428    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1429    /// #
1430    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1431    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1432    /// if let Ok(webhook) = client.create_webhook(&WebhookCreate::new("https://example.com")).await {
1433    ///     let _ = client.delete_webhook(&webhook.uuid.to_string()).await;
1434    /// }
1435    /// # });
1436    /// ```
1437    pub async fn delete_webhook(&self, uuid: impl AsRef<str>) -> Result<(), Error> {
1438        self.http_client
1439            .request::<(), (), ()>(
1440                &format!("{}/webhooks/{}", self.host, uuid.as_ref()),
1441                Method::Delete { query: () },
1442                204,
1443            )
1444            .await
1445    }
1446
1447    fn sleep_backend(&self) -> SleepBackend {
1448        SleepBackend::infer(self.http_client.is_tokio())
1449    }
1450}
1451
1452#[derive(Debug, Clone, Deserialize)]
1453#[serde(rename_all = "camelCase")]
1454pub struct ClientStats {
1455    /// Storage space claimed by Meilisearch and LMDB in bytes
1456    pub database_size: usize,
1457
1458    /// Storage space used by the database in bytes, excluding unused space claimed by LMDB
1459    pub used_database_size: usize,
1460
1461    /// When the last update was made to the database in the `RFC 3339` format
1462    #[serde(with = "time::serde::rfc3339::option")]
1463    pub last_update: Option<OffsetDateTime>,
1464
1465    /// The statistics for each index found in the database
1466    pub indexes: HashMap<String, IndexStats>,
1467}
1468
1469/// Health of the Meilisearch server.
1470///
1471/// # Example
1472///
1473/// ```
1474/// # use meilisearch_sdk::{client::*, indexes::*, errors::Error};
1475/// Health {
1476///     status: "available".to_string(),
1477/// };
1478/// ```
1479#[derive(Debug, Clone, Deserialize)]
1480pub struct Health {
1481    pub status: String,
1482}
1483
1484/// Version of a Meilisearch server.
1485///
1486/// # Example
1487///
1488/// ```
1489/// # use meilisearch_sdk::{client::*, indexes::*, errors::Error};
1490/// Version {
1491///     commit_sha: "b46889b5f0f2f8b91438a08a358ba8f05fc09fc1".to_string(),
1492///     commit_date: "2019-11-15T09:51:54.278247+00:00".to_string(),
1493///     pkg_version: "0.1.1".to_string(),
1494/// };
1495/// ```
1496#[derive(Debug, Clone, Deserialize)]
1497#[serde(rename_all = "camelCase")]
1498pub struct Version {
1499    pub commit_sha: String,
1500    pub commit_date: String,
1501    pub pkg_version: String,
1502}
1503
1504#[cfg(test)]
1505mod tests {
1506    use super::*;
1507    use crate::network::RemoteConfig;
1508    use crate::tasks::TaskType;
1509
1510    #[tokio::test]
1511    async fn test_get_network_state_parses_leader_and_version() {
1512        let mut s = mockito::Server::new_async().await;
1513        let base = s.url();
1514
1515        let response_body = serde_json::json!({
1516            "remotes": {
1517                "ms-00": {
1518                    "url": "http://ms-00",
1519                    "searchApiKey": "SEARCH",
1520                    "writeApiKey": "WRITE"
1521                },
1522                "ms-01": {
1523                    "url": "http://ms-01",
1524                    "searchApiKey": "SEARCH-1"
1525                }
1526            },
1527            "self": "ms-00",
1528            "leader": "ms-00",
1529            "version": "00000000-0000-0000-0000-000000000000"
1530        })
1531        .to_string();
1532
1533        let _m = s
1534            .mock("GET", "/network")
1535            .with_status(200)
1536            .with_header("content-type", "application/json")
1537            .with_body(response_body)
1538            .create_async()
1539            .await;
1540
1541        let client = Client::new(base, None::<String>).unwrap();
1542        let state = client.get_network_state().await.unwrap();
1543        assert_eq!(state.leader.as_deref(), Some("ms-00"));
1544        assert_eq!(
1545            state
1546                .version
1547                .expect("version should be present")
1548                .to_string(),
1549            "00000000-0000-0000-0000-000000000000"
1550        );
1551        let remotes = state.remotes.expect("remotes should be present");
1552        let ms00 = remotes.get("ms-00").expect("ms-00 should exist");
1553        assert_eq!(ms00.write_api_key.as_deref(), Some("WRITE"));
1554        let ms01 = remotes.get("ms-01").expect("ms-01 should exist");
1555        assert_eq!(ms01.write_api_key, None);
1556    }
1557
1558    #[tokio::test]
1559    async fn test_update_network_returns_task() {
1560        let mut s = mockito::Server::new_async().await;
1561        let base = s.url();
1562
1563        let response_body = serde_json::json!({
1564            "taskUid": 42,
1565            "indexUid": null,
1566            "status": "enqueued",
1567            "type": "networkTopologyChange",
1568            "enqueuedAt": "2024-10-11T11:49:53.000Z",
1569            "details": {
1570                "oldVersion": "00000000-0000-0000-0000-000000000000",
1571                "newVersion": "11111111-1111-1111-1111-111111111111"
1572            }
1573        })
1574        .to_string();
1575
1576        let _m = s
1577            .mock("PATCH", "/network")
1578            .with_status(202)
1579            .with_header("content-type", "application/json")
1580            .with_body(response_body)
1581            .create_async()
1582            .await;
1583
1584        let client = Client::new(base, None::<String>).unwrap();
1585        let update = NetworkUpdate {
1586            leader: Some("ms-01".to_string()),
1587            remotes: Some(std::iter::once(("ms-02".to_string(), None::<RemoteConfig>)).collect()),
1588            ..NetworkUpdate::default()
1589        };
1590
1591        let task = client
1592            .update_network_state(&update)
1593            .await
1594            .expect("update_network_state failed");
1595        assert_eq!(task.task_uid, 42);
1596        match task.update_type {
1597            TaskType::NetworkTopologyChange { details } => {
1598                let details = details.expect("details should be present");
1599                assert_eq!(
1600                    details
1601                        .info
1602                        .get("newVersion")
1603                        .and_then(|v| v.as_str())
1604                        .unwrap(),
1605                    "11111111-1111-1111-1111-111111111111"
1606                );
1607            }
1608            _ => panic!("expected NetworkTopologyChange task"),
1609        }
1610    }
1611
1612    use big_s::S;
1613    use time::OffsetDateTime;
1614
1615    use meilisearch_test_macro::meilisearch_test;
1616
1617    use crate::{key::Action, reqwest::qualified_version};
1618
1619    #[derive(Debug, Serialize, Deserialize, PartialEq)]
1620    struct Document {
1621        id: String,
1622    }
1623
1624    #[meilisearch_test]
1625    async fn test_swapping_two_indexes(client: Client) {
1626        let index_1 = client.index("test_swapping_two_indexes_1");
1627        let index_2 = client.index("test_swapping_two_indexes_2");
1628
1629        let t0 = index_1
1630            .add_documents(
1631                &[Document {
1632                    id: "1".to_string(),
1633                }],
1634                None,
1635            )
1636            .await
1637            .unwrap();
1638
1639        index_2
1640            .add_documents(
1641                &[Document {
1642                    id: "2".to_string(),
1643                }],
1644                None,
1645            )
1646            .await
1647            .unwrap();
1648
1649        t0.wait_for_completion(&client, None, None).await.unwrap();
1650
1651        let task = client
1652            .swap_indexes([&SwapIndexes {
1653                indexes: (
1654                    "test_swapping_two_indexes_1".to_string(),
1655                    "test_swapping_two_indexes_2".to_string(),
1656                ),
1657                rename: None,
1658            }])
1659            .await
1660            .unwrap();
1661        task.wait_for_completion(&client, None, None).await.unwrap();
1662
1663        let document = index_1.get_document("2").await.unwrap();
1664
1665        assert_eq!(
1666            Document {
1667                id: "2".to_string()
1668            },
1669            document
1670        );
1671    }
1672
1673    #[meilisearch_test]
1674    async fn test_methods_has_qualified_version_as_header() {
1675        let mut s = mockito::Server::new_async().await;
1676        let mock_server_url = s.url();
1677        let path = "/hello";
1678        let address = &format!("{mock_server_url}{path}");
1679        let user_agent = &*qualified_version();
1680        let client = Client::new(mock_server_url, None::<String>).unwrap();
1681
1682        let assertions = vec![
1683            (
1684                s.mock("GET", path)
1685                    .match_header("User-Agent", user_agent)
1686                    .create_async()
1687                    .await,
1688                client
1689                    .http_client
1690                    .request::<(), (), ()>(address, Method::Get { query: () }, 200),
1691            ),
1692            (
1693                s.mock("POST", path)
1694                    .match_header("User-Agent", user_agent)
1695                    .create_async()
1696                    .await,
1697                client.http_client.request::<(), (), ()>(
1698                    address,
1699                    Method::Post {
1700                        query: (),
1701                        body: {},
1702                    },
1703                    200,
1704                ),
1705            ),
1706            (
1707                s.mock("DELETE", path)
1708                    .match_header("User-Agent", user_agent)
1709                    .create_async()
1710                    .await,
1711                client.http_client.request::<(), (), ()>(
1712                    address,
1713                    Method::Delete { query: () },
1714                    200,
1715                ),
1716            ),
1717            (
1718                s.mock("PUT", path)
1719                    .match_header("User-Agent", user_agent)
1720                    .create_async()
1721                    .await,
1722                client.http_client.request::<(), (), ()>(
1723                    address,
1724                    Method::Put {
1725                        query: (),
1726                        body: (),
1727                    },
1728                    200,
1729                ),
1730            ),
1731            (
1732                s.mock("PATCH", path)
1733                    .match_header("User-Agent", user_agent)
1734                    .create_async()
1735                    .await,
1736                client.http_client.request::<(), (), ()>(
1737                    address,
1738                    Method::Patch {
1739                        query: (),
1740                        body: (),
1741                    },
1742                    200,
1743                ),
1744            ),
1745        ];
1746
1747        for (m, req) in assertions {
1748            let _ = req.await;
1749
1750            m.assert_async().await;
1751        }
1752    }
1753
1754    #[meilisearch_test]
1755    async fn test_get_tasks(client: Client) {
1756        let tasks = client.get_tasks().await.unwrap();
1757        assert_eq!(tasks.limit, 20);
1758    }
1759
1760    #[meilisearch_test]
1761    async fn test_rename_index_via_swap(client: Client, name: String) -> Result<(), Error> {
1762        let from = format!("{name}_from");
1763        let to = format!("{name}_to");
1764
1765        client
1766            .create_index(&from, None)
1767            .await?
1768            .wait_for_completion(&client, None, None)
1769            .await?;
1770
1771        let task = client
1772            .swap_indexes([&SwapIndexes {
1773                indexes: (from.clone(), to.clone()),
1774                rename: Some(true),
1775            }])
1776            .await?;
1777        task.wait_for_completion(&client, None, None).await?;
1778
1779        let new_index = client.get_index(&to).await?;
1780        assert_eq!(new_index.uid, to);
1781        // Optional: old uid should no longer resolve
1782        assert!(client.get_raw_index(&from).await.is_err());
1783
1784        new_index
1785            .delete()
1786            .await?
1787            .wait_for_completion(&client, None, None)
1788            .await?;
1789
1790        Ok(())
1791    }
1792
1793    #[meilisearch_test]
1794    async fn test_get_tasks_with_params(client: Client) {
1795        let query = TasksSearchQuery::new(&client);
1796        let tasks = client.get_tasks_with(&query).await.unwrap();
1797
1798        assert_eq!(tasks.limit, 20);
1799    }
1800
1801    #[meilisearch_test]
1802    async fn test_get_keys(client: Client) {
1803        let keys = client.get_keys().await.unwrap();
1804
1805        assert!(keys.results.len() >= 2);
1806    }
1807
1808    #[meilisearch_test]
1809    async fn test_delete_key(client: Client, name: String) {
1810        let mut key = KeyBuilder::new();
1811        key.with_name(&name);
1812        let key = client.create_key(key).await.unwrap();
1813
1814        client.delete_key(&key).await.unwrap();
1815        let keys = KeysQuery::new()
1816            .with_limit(10000)
1817            .execute(&client)
1818            .await
1819            .unwrap();
1820
1821        assert!(keys.results.iter().all(|k| k.key != key.key));
1822    }
1823
1824    #[meilisearch_test]
1825    async fn test_error_delete_key(client: Client, name: String) {
1826        // ==> accessing a key that does not exist
1827        let error = client.delete_key("invalid_key").await.unwrap_err();
1828        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");
1829
1830        // ==> executing the action without enough right
1831        let mut key = KeyBuilder::new();
1832
1833        key.with_name(&name);
1834        let key = client.create_key(key).await.unwrap();
1835        let master_key = client.api_key.clone();
1836
1837        // create a new client with no right
1838        let client = Client::new(client.host, Some(key.key.clone())).unwrap();
1839        // with a wrong key
1840        let error = client.delete_key("invalid_key").await.unwrap_err();
1841        insta::assert_snapshot!(error, @"Meilisearch auth: invalid_api_key: The provided API key is invalid.. https://docs.meilisearch.com/errors#invalid_api_key");
1842        assert!(matches!(
1843            error,
1844            Error::Meilisearch(MeilisearchError {
1845                error_code: ErrorCode::InvalidApiKey,
1846                error_type: ErrorType::Auth,
1847                ..
1848            })
1849        ));
1850        // with a good key
1851        let error = client.delete_key(&key.key).await.unwrap_err();
1852        insta::assert_snapshot!(error, @"Meilisearch auth: invalid_api_key: The provided API key is invalid.. https://docs.meilisearch.com/errors#invalid_api_key");
1853        assert!(matches!(
1854            error,
1855            Error::Meilisearch(MeilisearchError {
1856                error_code: ErrorCode::InvalidApiKey,
1857                error_type: ErrorType::Auth,
1858                ..
1859            })
1860        ));
1861
1862        // cleanup
1863        let client = Client::new(client.host, master_key).unwrap();
1864        client.delete_key(key).await.unwrap();
1865    }
1866
1867    #[meilisearch_test]
1868    async fn test_create_key(client: Client, name: String) {
1869        let expires_at = OffsetDateTime::now_utc() + time::Duration::HOUR;
1870        let mut key = KeyBuilder::new();
1871        key.with_action(Action::DocumentsAdd)
1872            .with_name(&name)
1873            .with_expires_at(expires_at)
1874            .with_description("a description")
1875            .with_index("*");
1876        let key = client.create_key(key).await.unwrap();
1877
1878        assert_eq!(key.actions, vec![Action::DocumentsAdd]);
1879        assert_eq!(&key.name, &Some(name));
1880        // We can't compare the two timestamps directly because of some nanoseconds imprecision with the floats
1881        assert_eq!(
1882            key.expires_at.unwrap().unix_timestamp(),
1883            expires_at.unix_timestamp()
1884        );
1885        assert_eq!(key.indexes, vec![S("*")]);
1886
1887        client.delete_key(key).await.unwrap();
1888    }
1889
1890    #[meilisearch_test]
1891    async fn test_error_create_key(client: Client, name: String) {
1892        // ==> Invalid index name
1893        /* TODO: uncomment once meilisearch fix this bug: https://github.com/meilisearch/meilisearch/issues/2158
1894        let mut key = KeyBuilder::new();
1895        key.with_index("invalid index # / \\name with spaces");
1896        let error = client.create_key(key).await.unwrap_err();
1897
1898        assert!(matches!(
1899            error,
1900            Error::MeilisearchError {
1901                error_code: ErrorCode::InvalidApiKeyIndexes,
1902                error_type: ErrorType::InvalidRequest,
1903                ..
1904            }
1905        ));
1906        */
1907        // ==> executing the action without enough right
1908        let mut no_right_key = KeyBuilder::new();
1909        no_right_key.with_name(format!("{name}_1"));
1910        let no_right_key = client.create_key(no_right_key).await.unwrap();
1911
1912        // backup the master key for cleanup at the end of the test
1913        let master_client = client.clone();
1914        let client = Client::new(&master_client.host, Some(no_right_key.key.clone())).unwrap();
1915
1916        let mut key = KeyBuilder::new();
1917        key.with_name(format!("{name}_2"));
1918        let error = client.create_key(key).await.unwrap_err();
1919
1920        assert!(matches!(
1921            error,
1922            Error::Meilisearch(MeilisearchError {
1923                error_code: ErrorCode::InvalidApiKey,
1924                error_type: ErrorType::Auth,
1925                ..
1926            })
1927        ));
1928
1929        // cleanup
1930        master_client
1931            .delete_key(client.api_key.unwrap())
1932            .await
1933            .unwrap();
1934    }
1935
1936    #[meilisearch_test]
1937    async fn test_update_key(client: Client, description: String) {
1938        let mut key = KeyBuilder::new();
1939        key.with_name("test_update_key");
1940        let mut key = client.create_key(key).await.unwrap();
1941
1942        let name = S("new name");
1943        key.with_description(&description);
1944        key.with_name(&name);
1945
1946        let key = key.update(&client).await.unwrap();
1947
1948        assert_eq!(key.description, Some(description));
1949        assert_eq!(key.name, Some(name));
1950
1951        client.delete_key(key).await.unwrap();
1952    }
1953
1954    #[meilisearch_test]
1955    async fn test_get_index(client: Client, index_uid: String) -> Result<(), Error> {
1956        let task = client.create_index(&index_uid, None).await?;
1957        let index = client
1958            .wait_for_task(task, None, None)
1959            .await?
1960            .try_make_index(&client)
1961            .unwrap();
1962
1963        assert_eq!(index.uid, index_uid);
1964        index
1965            .delete()
1966            .await?
1967            .wait_for_completion(&client, None, None)
1968            .await?;
1969        Ok(())
1970    }
1971
1972    #[meilisearch_test]
1973    async fn test_error_create_index(client: Client, index: Index) -> Result<(), Error> {
1974        let error = client
1975            .create_index("Wrong index name", None)
1976            .await
1977            .unwrap_err();
1978
1979        assert!(matches!(
1980            error,
1981            Error::Meilisearch(MeilisearchError {
1982                error_code: ErrorCode::InvalidIndexUid,
1983                error_type: ErrorType::InvalidRequest,
1984                ..
1985            })
1986        ));
1987
1988        // we try to create an index with the same uid of an already existing index
1989        let error = client
1990            .create_index(&*index.uid, None)
1991            .await?
1992            .wait_for_completion(&client, None, None)
1993            .await?
1994            .unwrap_failure();
1995
1996        assert!(matches!(
1997            error,
1998            MeilisearchError {
1999                error_code: ErrorCode::IndexAlreadyExists,
2000                error_type: ErrorType::InvalidRequest,
2001                ..
2002            }
2003        ));
2004        Ok(())
2005    }
2006
2007    #[meilisearch_test]
2008    async fn test_list_all_indexes(client: Client) {
2009        let all_indexes = client.list_all_indexes().await.unwrap();
2010
2011        assert_eq!(all_indexes.limit, 20);
2012        assert_eq!(all_indexes.offset, 0);
2013    }
2014
2015    #[meilisearch_test]
2016    async fn test_list_all_indexes_with_params(client: Client) {
2017        let mut query = IndexesQuery::new(&client);
2018        query.with_limit(1);
2019        let all_indexes = client.list_all_indexes_with(&query).await.unwrap();
2020
2021        assert_eq!(all_indexes.limit, 1);
2022        assert_eq!(all_indexes.offset, 0);
2023    }
2024
2025    #[meilisearch_test]
2026    async fn test_list_all_indexes_raw(client: Client) {
2027        let all_indexes_raw = client.list_all_indexes_raw().await.unwrap();
2028
2029        assert_eq!(all_indexes_raw["limit"], json!(20));
2030        assert_eq!(all_indexes_raw["offset"], json!(0));
2031    }
2032
2033    #[meilisearch_test]
2034    async fn test_list_all_indexes_raw_with_params(client: Client) {
2035        let mut query = IndexesQuery::new(&client);
2036        query.with_limit(1);
2037        let all_indexes_raw = client.list_all_indexes_raw_with(&query).await.unwrap();
2038
2039        assert_eq!(all_indexes_raw["limit"], json!(1));
2040        assert_eq!(all_indexes_raw["offset"], json!(0));
2041    }
2042
2043    #[meilisearch_test]
2044    async fn test_get_primary_key_is_none(mut index: Index) {
2045        let primary_key = index.get_primary_key().await;
2046
2047        assert!(primary_key.is_ok());
2048        assert!(primary_key.unwrap().is_none());
2049    }
2050
2051    #[meilisearch_test]
2052    async fn test_get_primary_key(client: Client, index_uid: String) -> Result<(), Error> {
2053        let mut index = client
2054            .create_index(index_uid, Some("primary_key"))
2055            .await?
2056            .wait_for_completion(&client, None, None)
2057            .await?
2058            .try_make_index(&client)
2059            .unwrap();
2060
2061        let primary_key = index.get_primary_key().await;
2062        assert!(primary_key.is_ok());
2063        assert_eq!(primary_key?.unwrap(), "primary_key");
2064
2065        index
2066            .delete()
2067            .await?
2068            .wait_for_completion(&client, None, None)
2069            .await?;
2070
2071        Ok(())
2072    }
2073}