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}