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}