From 57cde0a839af245d7ae5417e355670d925c264fc Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Thu, 9 Jun 2022 22:58:56 +0200 Subject: [PATCH 01/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd948de0..64821aca 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ WARNING: `meilisearch-sdk` will panic if no Window is available (ex: Web extensi ## 🤖 Compatibility with Meilisearch -This package only guarantees the compatibility with the [version v0.27.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.27.0). +This package only guarantees the compatibility with the [version v0.28.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.28.0). ## ⚙️ Development Workflow and Contributing From f46f78d17a0662c6dd9650f21b0cde4e4cef7ed0 Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Thu, 9 Jun 2022 22:58:57 +0200 Subject: [PATCH 02/19] Update README.tpl --- README.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.tpl b/README.tpl index 04e98565..2a519a08 100644 --- a/README.tpl +++ b/README.tpl @@ -97,7 +97,7 @@ WARNING: `meilisearch-sdk` will panic if no Window is available (ex: Web extensi ## 🤖 Compatibility with Meilisearch -This package only guarantees the compatibility with the [version v0.27.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.27.0). +This package only guarantees the compatibility with the [version v0.28.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.28.0). ## ⚙️ Development Workflow and Contributing From 0351c8aefcf5498dd7dcabd2df67c6df34e080a8 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 22 Aug 2022 13:35:15 +0200 Subject: [PATCH 03/19] Update task api for v0.28.0 (#299) * Update task api v0.28.0 * Update tests to be relevant with the structures where they are ran * Update types in code-samples * Remove clearAll task type * Add index_uid as an optional in the Tasks * Add comments on best builder implementation * Add execute function on get_tasks * Update code samples * Remove out of context comment * Fix tests * Fix pagination tests * Fix clippy * Fix doc tests * Fix get tasks inside index structure * Fix none doc tests with new get_tasks api * Add from and limit in tasks params * Add warning on failing test * Update task API * Remove useless comment * Rename type in Task structure * Removed useless newlines * Fix typo in comment * Add missing semi-column --- .code-samples.meilisearch.yaml | 86 ++++---- meilisearch-test-macro/README.md | 4 +- src/client.rs | 82 ++++++-- src/indexes.rs | 148 ++++++++------ src/lib.rs | 4 + src/search.rs | 3 +- src/settings.rs | 74 +++---- src/task_info.rs | 177 +++++++++++++++++ src/tasks.rs | 323 ++++++++++++++++++++----------- src/utils.rs | 45 +++++ 10 files changed, 679 insertions(+), 267 deletions(-) create mode 100644 src/task_info.rs create mode 100644 src/utils.rs diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index d19f3d34..eb7673ab 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -18,7 +18,7 @@ get_one_document_1: |- get_documents_1: |- let documents: Vec = client.index("movies").get_documents(None, Some(2), None).await.unwrap(); add_or_replace_documents_1: |- - let task: Task = client.index("movies").add_or_replace(&[ + let task: TaskInfo = client.index("movies").add_or_replace(&[ Movie { id: 287947, title: "Shazam".to_string(), @@ -35,18 +35,18 @@ add_or_update_documents_1: |- title: String } - let task: Task = client.index("movies").add_or_update(&[ + let task: TaskInfo = client.index("movies").add_or_update(&[ IncompleteMovie { id: 287947, title: "Shazam ⚡️".to_string() } ], None).await.unwrap(); delete_all_documents_1: |- - let task: Task = client.index("movies").delete_all_documents().await.unwrap(); + let task: TaskInfo = client.index("movies").delete_all_documents().await.unwrap(); delete_one_document_1: |- - let task: Task = client.index("movies").delete_document(25684).await.unwrap(); + let task: TaskInfo = client.index("movies").delete_document(25684).await.unwrap(); delete_documents_1: |- - let task: Task = client.index("movies").delete_documents(&[23488, 153738, 437035, 363869]).await.unwrap(); + let task: TaskInfo = client.index("movies").delete_documents(&[23488, 153738, 437035, 363869]).await.unwrap(); search_post_1: |- let results: SearchResults = client.index("movies") .search() @@ -57,9 +57,9 @@ search_post_1: |- get_task_by_index_1: |- let task: Task = client.index("movies").get_task(1).await.unwrap(); get_all_tasks_by_index_1: |- - let tasks: Vec = client.index("movies").get_tasks().await.unwrap(); + let tasks: TasksResults = client.index("movies").get_tasks().await.unwrap(); get_all_tasks_1: |- - let tasks: Vec = client.get_tasks().await.unwrap(); + let tasks: TasksResults = client.get_tasks().await.unwrap(); get_task_1: |- let task: Task = client.get_task(1).await.unwrap(); get_settings_1: |- @@ -103,9 +103,9 @@ update_settings_1: |- ]) .with_synonyms(synonyms); - let task: Task = client.index("movies").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("movies").set_settings(&settings).await.unwrap(); reset_settings_1: |- - let task: Task = client.index("movies").reset_settings().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_settings().await.unwrap(); get_synonyms_1: |- let synonyms: HashMap> = client.index("movies").get_synonyms().await.unwrap(); update_synonyms_1: |- @@ -114,16 +114,16 @@ update_synonyms_1: |- synonyms.insert(String::from("logan"), vec![String::from("xmen"), String::from("wolverine")]); synonyms.insert(String::from("wow"), vec![String::from("world of warcraft")]); - let task: Task = client.index("movies").set_synonyms(&synonyms).await.unwrap(); + let task: TaskInfo = client.index("movies").set_synonyms(&synonyms).await.unwrap(); reset_synonyms_1: |- - let task: Task = client.index("movies").reset_synonyms().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_synonyms().await.unwrap(); get_stop_words_1: |- let stop_words: Vec = client.index("movies").get_stop_words().await.unwrap(); update_stop_words_1: |- let stop_words = ["of", "the", "to"]; - let task: Task = client.index("movies").set_stop_words(&stop_words).await.unwrap(); + let task: TaskInfo = client.index("movies").set_stop_words(&stop_words).await.unwrap(); reset_stop_words_1: |- - let task: Task = client.index("movies").reset_stop_words().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_stop_words().await.unwrap(); get_ranking_rules_1: |- let ranking_rules: Vec = client.index("movies").get_ranking_rules().await.unwrap(); update_ranking_rules_1: |- @@ -138,15 +138,15 @@ update_ranking_rules_1: |- "rank:desc", ]; - let task: Task = client.index("movies").set_ranking_rules(&ranking_rules).await.unwrap(); + let task: TaskInfo = client.index("movies").set_ranking_rules(&ranking_rules).await.unwrap(); reset_ranking_rules_1: |- - let task: Task = client.index("movies").reset_ranking_rules().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_ranking_rules().await.unwrap(); get_distinct_attribute_1: |- let distinct_attribute: Option = client.index("shoes").get_distinct_attribute().await.unwrap(); update_distinct_attribute_1: |- - let task: Task = client.index("shoes").set_distinct_attribute("skuid").await.unwrap(); + let task: TaskInfo = client.index("shoes").set_distinct_attribute("skuid").await.unwrap(); reset_distinct_attribute_1: |- - let task: Task = client.index("shoes").reset_distinct_attribute().await.unwrap(); + let task: TaskInfo = client.index("shoes").reset_distinct_attribute().await.unwrap(); get_searchable_attributes_1: |- let searchable_attributes: Vec = client.index("movies").get_searchable_attributes().await.unwrap(); update_searchable_attributes_1: |- @@ -156,9 +156,9 @@ update_searchable_attributes_1: |- "genres" ]; - let task: Task = client.index("movies").set_searchable_attributes(&searchable_attributes).await.unwrap(); + let task: TaskInfo = client.index("movies").set_searchable_attributes(&searchable_attributes).await.unwrap(); reset_searchable_attributes_1: |- - let task: Task = client.index("movies").reset_searchable_attributes().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_searchable_attributes().await.unwrap(); get_filterable_attributes_1: |- let filterable_attributes: Vec = client.index("movies").get_filterable_attributes().await.unwrap(); update_filterable_attributes_1: |- @@ -167,9 +167,9 @@ update_filterable_attributes_1: |- "director" ]; - let task: Task = client.index("movies").set_filterable_attributes(&filterable_attributes).await.unwrap(); + let task: TaskInfo = client.index("movies").set_filterable_attributes(&filterable_attributes).await.unwrap(); reset_filterable_attributes_1: |- - let task: Task = client.index("movies").reset_filterable_attributes().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_filterable_attributes().await.unwrap(); get_displayed_attributes_1: |- let displayed_attributes: Vec = client.index("movies").get_displayed_attributes().await.unwrap(); update_displayed_attributes_1: |- @@ -180,9 +180,9 @@ update_displayed_attributes_1: |- "release_date" ]; - let task: Task = client.index("movies").set_displayed_attributes(&displayed_attributes).await.unwrap(); + let task: TaskInfo = client.index("movies").set_displayed_attributes(&displayed_attributes).await.unwrap(); reset_displayed_attributes_1: |- - let task: Task = client.index("movies").reset_displayed_attributes().await.unwrap(); + let task: TaskInfo = client.index("movies").reset_displayed_attributes().await.unwrap(); get_index_stats_1: |- let stats: IndexStats = client.index("movies").get_stats().await.unwrap(); get_indexes_stats_1: |- @@ -193,7 +193,7 @@ get_health_1: |- get_version_1: |- let version: Version = client.get_version().await.unwrap(); distinct_attribute_guide_1: |- - let task: Task = client.index("jackets").set_distinct_attribute("product_id").await.unwrap(); + let task: TaskInfo = client.index("jackets").set_distinct_attribute("product_id").await.unwrap(); field_properties_guide_searchable_1: |- let searchable_attributes = [ "title", @@ -201,7 +201,7 @@ field_properties_guide_searchable_1: |- "genres" ]; - let task: Task = client.index("movies").set_searchable_attributes(&searchable_attributes).await.unwrap(); + let task: TaskInfo = client.index("movies").set_searchable_attributes(&searchable_attributes).await.unwrap(); field_properties_guide_displayed_1: |- let displayed_attributes = [ "title", @@ -210,7 +210,7 @@ field_properties_guide_displayed_1: |- "release_date" ]; - let task: Task = client.index("movies").set_displayed_attributes(&displayed_attributes).await.unwrap(); + let task: TaskInfo = client.index("movies").set_displayed_attributes(&displayed_attributes).await.unwrap(); filtering_guide_1: |- let results: SearchResults = client.index("movies").search() .with_query("Avengers") @@ -330,7 +330,7 @@ settings_guide_stop_words_1: |- "an" ]); - let task = client.index("movies").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("movies").set_settings(&settings).await.unwrap(); settings_guide_filterable_attributes_1: |- let settings = Settings::new() .with_filterable_attributes([ @@ -338,7 +338,7 @@ settings_guide_filterable_attributes_1: |- "genres" ]); - let task: Task = client.index("movies").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("movies").set_settings(&settings).await.unwrap(); settings_guide_ranking_rules_1: |- let settings = Settings::new() .with_ranking_rules([ @@ -352,12 +352,12 @@ settings_guide_ranking_rules_1: |- "rank:desc", ]); - let task = client.index("movies").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("movies").set_settings(&settings).await.unwrap(); settings_guide_distinct_1: |- let settings = Settings::new() .with_distinct_attribute("product_id"); - let task: Task = client.index("jackets").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("jackets").set_settings(&settings).await.unwrap(); settings_guide_searchable_1: |- let settings = Settings::new() .with_searchable_attributes([ @@ -366,7 +366,7 @@ settings_guide_searchable_1: |- "genres" ]); - let task: Task = client.index("movies").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("movies").set_settings(&settings).await.unwrap(); settings_guide_displayed_1: |- let settings = Settings::new() .with_displayed_attributes([ @@ -376,7 +376,7 @@ settings_guide_displayed_1: |- "release_date" ]); - let task: Task = client.index("movies").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("movies").set_settings(&settings).await.unwrap(); settings_guide_sortable_1: |- let settings = Settings::new() .with_sortable_attributes([ @@ -384,7 +384,7 @@ settings_guide_sortable_1: |- "price" ]); - let task: Task = client.index("books").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("books").set_settings(&settings).await.unwrap(); add_movies_json_1: |- use meilisearch_sdk::{ indexes::*, @@ -417,7 +417,7 @@ documents_guide_add_movie_1: |- } // Add a document to our index - let task: Task = client.index("movies").add_documents(&[ + let task: TaskInfo = client.index("movies").add_documents(&[ IncompleteMovie { id: "123sq178".to_string(), title: "Amélie Poulain".to_string(), @@ -437,7 +437,7 @@ primary_field_guide_add_document_primary_key: |- price: f64 } - let task: Task = client.index("books").add_documents(&[ + let task: TaskInfo = client.index("books").add_documents(&[ Book { reference_number: "287947".to_string(), title: "Diary of a Wimpy Kid".to_string(), @@ -616,7 +616,7 @@ getting_started_configure_settings: |- "mass", "_geo" ]) - let task: Task = client.index("meteorites").set_settings(&settings).await.unwrap(); + let task: TaskInfo = client.index("meteorites").set_settings(&settings).await.unwrap(); getting_started_geo_radius: |- let results: SearchResults = client.index("meteorites").search() .with_filter("_geoRadius(46.9480, 7.4474, 210000)") @@ -643,7 +643,7 @@ getting_started_filtering: |- .await .unwrap(); faceted_search_update_settings_1: |- - let task: Task = client.index("movies").set_filterable_attributes(["director", "genres"]).await.unwrap(); + let task: TaskInfo = client.index("movies").set_filterable_attributes(["director", "genres"]).await.unwrap(); faceted_search_filter_1: |- let results: SearchResults = client.index("movies").search() .with_query("thriller") @@ -683,7 +683,7 @@ sorting_guide_update_sortable_attributes_1: |- "price" ]; - let task: Task = client.index("books").set_sortable_attributes(&sortable_attributes).await.unwrap(); + let task: TaskInfo = client.index("books").set_sortable_attributes(&sortable_attributes).await.unwrap(); sorting_guide_update_ranking_rules_1: |- let ranking_rules = [ "words", @@ -694,7 +694,7 @@ sorting_guide_update_ranking_rules_1: |- "exactness" ]; - let task: Task = client.index("books").set_ranking_rules(&ranking_rules).await.unwrap(); + let task: TaskInfo = client.index("books").set_ranking_rules(&ranking_rules).await.unwrap(); sorting_guide_sort_parameter_1: |- let results: SearchResults = client.index("books").search() .with_query("science fiction") @@ -717,9 +717,9 @@ update_sortable_attributes_1: |- "author" ]; - let task: Task = client.index("books").set_sortable_attributes(&sortable_attributes).await.unwrap(); + let task: TaskInfo = client.index("books").set_sortable_attributes(&sortable_attributes).await.unwrap(); reset_sortable_attributes_1: |- - let task: Task = client.index("books").reset_sortable_attributes().await.unwrap(); + let task: TaskInfo = client.index("books").reset_sortable_attributes().await.unwrap(); search_parameter_guide_sort_1: |- let results: SearchResults = client.index("books").search() .with_query("science fiction") @@ -728,7 +728,7 @@ search_parameter_guide_sort_1: |- .await .unwrap(); geosearch_guide_filter_settings_1: |- - let task: Task = client.index("restaurants").set_filterable_attributes(&["_geo"]).await.unwrap(); + let task: TaskInfo = client.index("restaurants").set_filterable_attributes(&["_geo"]).await.unwrap(); geosearch_guide_filter_usage_1: |- let results: SearchResults = client.index("restaurants").search() .with_filter("_geoRadius(45.472735, 9.184019, 2000)") @@ -742,7 +742,7 @@ geosearch_guide_filter_usage_2: |- .await .unwrap(); geosearch_guide_sort_settings_1: |- - let task: Task = client.index("restaurants").set_sortable_attributes(&["_geo"]).await.unwrap(); + let task: TaskInfo = client.index("restaurants").set_sortable_attributes(&["_geo"]).await.unwrap(); geosearch_guide_sort_usage_1: |- let results: SearchResults = client.index("restaurants").search() .with_sort(&["_geoPoint(48.8561446, 2.2978204):asc"]) diff --git a/meilisearch-test-macro/README.md b/meilisearch-test-macro/README.md index fb1796ab..95d09d4a 100644 --- a/meilisearch-test-macro/README.md +++ b/meilisearch-test-macro/README.md @@ -25,7 +25,7 @@ async fn test_get_tasks() -> Result<(), Error> { let tasks = index.get_tasks().await?; // The only task is the creation of the index - assert_eq!(status.len(), 1); + assert_eq!(status.results.len(), 1); index.delete() .await? @@ -52,7 +52,7 @@ With this macro, all these problems are solved. See a rewrite of this test: async fn test_get_tasks(index: Index, client: Client) -> Result<(), Error> { let tasks = index.get_tasks().await?; // The only task is the creation of the index - assert_eq!(status.len(), 1); + assert_eq!(status.results.len(), 1); } ``` diff --git a/src/client.rs b/src/client.rs index 18f581b0..450bb583 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,9 @@ use crate::{ indexes::*, key::{Key, KeyBuilder}, request::*, - tasks::{async_sleep, Task}, + task_info::TaskInfo, + tasks::{Task, TasksQuery, TasksResults}, + utils::async_sleep, }; use serde::Deserialize; use serde_json::{json, Value}; @@ -197,8 +199,8 @@ impl Client { &self, uid: impl AsRef, primary_key: Option<&str>, - ) -> Result { - request::( + ) -> Result { + request::( &format!("{}/indexes", self.host), &self.api_key, Method::Post(json!({ @@ -212,8 +214,8 @@ impl Client { /// Delete an index from its UID. /// To delete an [Index], use the [Index::delete] method. - pub async fn delete_index(&self, uid: impl AsRef) -> Result { - request::<(), Task>( + pub async fn delete_index(&self, uid: impl AsRef) -> Result { + request::<(), TaskInfo>( &format!("{}/indexes/{}", self.host, uid.as_ref()), &self.api_key, Method::Delete, @@ -430,6 +432,7 @@ impl Client { /// let mut key = KeyBuilder::new("create_key"); /// key.with_index("*").with_action(Action::DocumentsAdd); /// let key = client.create_key(key).await.unwrap(); + /// /// assert_eq!(key.description, "create_key"); /// # client.delete_key(key).await.unwrap(); /// # }); @@ -511,7 +514,7 @@ impl Client { /// /// If the waited time exceeds `timeout` then an [Error::Timeout] will be returned. /// - /// See also [Index::wait_for_task, Task::wait_for_completion]. + /// See also [Index::wait_for_task, Task::wait_for_completion, TaskInfo::wait_for_completion]. /// /// # Example /// @@ -548,7 +551,7 @@ impl Client { /// ``` pub async fn wait_for_task( &self, - task_id: impl AsRef, + task_id: impl AsRef, interval: Option, timeout: Option, ) -> Result { @@ -560,7 +563,6 @@ impl Client { while timeout > elapsed_time { task_result = self.get_task(&task_id).await; - match task_result { Ok(status) => match status { Task::Failed { .. } | Task::Succeeded { .. } => { @@ -596,7 +598,7 @@ impl Client { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn get_task(&self, task_id: impl AsRef) -> Result { + pub async fn get_task(&self, task_id: impl AsRef) -> Result { request::<(), Task>( &format!("{}/tasks/{}", self.host, task_id.as_ref()), &self.api_key, @@ -606,6 +608,41 @@ impl Client { .await } + /// Get all tasks with query parameters from the server. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::*; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = client::Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut query = tasks::TasksQuery::new(&client); + /// query.with_index_uid(["get_tasks_with"]); + /// let tasks = client.get_tasks_with(&query).await.unwrap(); + /// + /// # assert!(tasks.results.len() > 0); + /// # client.index("get_tasks_with").delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn get_tasks_with( + &self, + tasks_query: &TasksQuery<'_>, + ) -> Result { + let tasks = request::<&TasksQuery, TasksResults>( + &format!("{}/tasks", self.host), + &self.api_key, + Method::Get(tasks_query), + 200, + ) + .await?; + + Ok(tasks) + } + /// Get all tasks from the server. /// /// # Example @@ -619,15 +656,13 @@ impl Client { /// # futures::executor::block_on(async move { /// # let client = client::Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// let tasks = client.get_tasks().await.unwrap(); + /// + /// # assert!(tasks.results.len() > 0); + /// # client.index("get_tasks").delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn get_tasks(&self) -> Result, Error> { - #[derive(Deserialize)] - struct Tasks { - pub results: Vec, - } - - let tasks = request::<(), Tasks>( + pub async fn get_tasks(&self) -> Result { + let tasks = request::<(), TasksResults>( &format!("{}/tasks", self.host), &self.api_key, Method::Get(()), @@ -635,7 +670,7 @@ impl Client { ) .await?; - Ok(tasks.results) + Ok(tasks) } /// Generates a new tenant token. @@ -769,6 +804,19 @@ mod tests { } } + #[meilisearch_test] + async fn test_get_tasks(client: Client) { + let tasks = client.get_tasks().await.unwrap(); + assert!(tasks.results.len() >= 2); + } + + #[meilisearch_test] + async fn test_get_tasks_with_params(client: Client) { + let query = TasksQuery::new(&client); + let tasks = client.get_tasks_with(&query).await.unwrap(); + assert!(tasks.results.len() >= 2); + } + #[meilisearch_test] async fn test_get_keys(client: Client) { let keys = client.get_keys().await.unwrap(); diff --git a/src/indexes.rs b/src/indexes.rs index 2e0dad4d..54294f69 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -1,4 +1,4 @@ -use crate::{client::Client, errors::Error, request::*, search::*, tasks::*}; +use crate::{client::Client, errors::Error, request::*, search::*, task_info::TaskInfo, tasks::*}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::json; use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration}; @@ -124,8 +124,8 @@ impl Index { /// client.wait_for_task(task, None, None).await.unwrap(); /// # }); /// ``` - pub async fn delete(self) -> Result { - request::<(), Task>( + pub async fn delete(self) -> Result { + request::<(), TaskInfo>( &format!("{}/indexes/{}", self.client.host, self.uid), &self.client.api_key, Method::Delete, @@ -391,7 +391,7 @@ impl Index { &self, documents: &[T], primary_key: Option<&str>, - ) -> Result { + ) -> Result { let url = if let Some(primary_key) = primary_key { format!( "{}/indexes/{}/documents?primaryKey={}", @@ -400,7 +400,7 @@ impl Index { } else { format!("{}/indexes/{}/documents", self.client.host, self.uid) }; - request::<&[T], Task>(&url, &self.client.api_key, Method::Post(documents), 202).await + request::<&[T], TaskInfo>(&url, &self.client.api_key, Method::Post(documents), 202).await } /// Alias for [Index::add_or_replace]. @@ -408,7 +408,7 @@ impl Index { &self, documents: &[T], primary_key: Option<&str>, - ) -> Result { + ) -> Result { self.add_or_replace(documents, primary_key).await } @@ -469,7 +469,7 @@ impl Index { &self, documents: &[T], primary_key: Option>, - ) -> Result { + ) -> Result { let url = if let Some(primary_key) = primary_key { format!( "{}/indexes/{}/documents?primaryKey={}", @@ -480,7 +480,7 @@ impl Index { } else { format!("{}/indexes/{}/documents", self.client.host, self.uid) }; - request::<&[T], Task>(&url, &self.client.api_key, Method::Put(documents), 202).await + request::<&[T], TaskInfo>(&url, &self.client.api_key, Method::Put(documents), 202).await } /// Delete all documents in the index. @@ -520,8 +520,8 @@ impl Index { /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn delete_all_documents(&self) -> Result { - request::<(), Task>( + pub async fn delete_all_documents(&self) -> Result { + request::<(), TaskInfo>( &format!("{}/indexes/{}/documents", self.client.host, self.uid), &self.client.api_key, Method::Delete, @@ -565,8 +565,8 @@ impl Index { /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn delete_document(&self, uid: T) -> Result { - request::<(), Task>( + pub async fn delete_document(&self, uid: T) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/documents/{}", self.client.host, self.uid, uid @@ -617,8 +617,8 @@ impl Index { pub async fn delete_documents( &self, uids: &[T], - ) -> Result { - request::<&[T], Task>( + ) -> Result { + request::<&[T], TaskInfo>( &format!( "{}/indexes/{}/documents/delete-batch", self.client.host, self.uid @@ -729,18 +729,13 @@ impl Index { /// Task::Succeeded { content } => content.uid, /// }; /// - /// assert_eq!(task.get_uid(), from_index); + /// assert_eq!(task.get_task_uid(), from_index); /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn get_task(&self, uid: impl AsRef) -> Result { + pub async fn get_task(&self, uid: impl AsRef) -> Result { request::<(), Task>( - &format!( - "{}/indexes/{}/tasks/{}", - self.client.host, - self.uid, - uid.as_ref() - ), + &format!("{}/tasks/{}", self.client.host, uid.as_ref()), &self.client.api_key, Method::Get(()), 200, @@ -763,30 +758,50 @@ impl Index { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// # let index = client.create_index("get_tasks", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); /// - /// let status = index.get_tasks().await.unwrap(); - /// assert!(status.len() == 1); // the index was created + /// let tasks = index.get_tasks().await.unwrap(); + /// + /// assert!(tasks.results.len() > 0); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn get_tasks(&self) -> Result { + let mut query = TasksQuery::new(&self.client); + query.with_index_uid([self.uid.as_str()]); + + self.client.get_tasks_with(&query).await + } + + /// Get the status of all tasks in a given index. + /// + /// # Example + /// + /// ``` + /// # use serde::{Serialize, Deserialize}; + /// # use meilisearch_sdk::{client::*, indexes::*, tasks::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let index = client.create_index("get_tasks_with", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); /// - /// index.set_ranking_rules(["wrong_ranking_rule"]).await.unwrap(); + /// let mut query = TasksQuery::new(&client); + /// query.with_index_uid(["none_existant"]); + /// let tasks = index.get_tasks_with(&query).await.unwrap(); /// - /// let status = index.get_tasks().await.unwrap(); - /// assert!(status.len() == 2); + /// assert!(tasks.results.len() > 0); /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn get_tasks(&self) -> Result, Error> { - #[derive(Deserialize)] - struct AllTasks { - results: Vec, - } + pub async fn get_tasks_with( + &self, + tasks_query: &TasksQuery<'_>, + ) -> Result { + let mut query = tasks_query.clone(); + query.with_index_uid([self.uid.as_str()]); - Ok(request::<(), AllTasks>( - &format!("{}/indexes/{}/tasks", self.client.host, self.uid), - &self.client.api_key, - Method::Get(()), - 200, - ) - .await? - .results) + self.client.get_tasks_with(&query).await } /// Get stats of an index. @@ -861,7 +876,7 @@ impl Index { /// ``` pub async fn wait_for_task( &self, - task_id: impl AsRef, + task_id: impl AsRef, interval: Option, timeout: Option, ) -> Result { @@ -925,7 +940,7 @@ impl Index { documents: &[T], batch_size: Option, primary_key: Option<&str>, - ) -> Result, Error> { + ) -> Result, Error> { let mut task = Vec::with_capacity(documents.len()); for document_batch in documents.chunks(batch_size.unwrap_or(1000)) { task.push(self.add_documents(document_batch, primary_key).await?); @@ -1015,7 +1030,7 @@ impl Index { documents: &[T], batch_size: Option, primary_key: Option<&str>, - ) -> Result, Error> { + ) -> Result, Error> { let mut task = Vec::with_capacity(documents.len()); for document_batch in documents.chunks(batch_size.unwrap_or(1000)) { task.push(self.add_or_update(document_batch, primary_key).await?); @@ -1085,13 +1100,6 @@ mod tests { assert!(index.primary_key.is_none()); } - #[meilisearch_test] - async fn test_get_tasks_no_docs(index: Index) { - // The at this point the only task that is supposed to exist is the creation of the index - let status = index.get_tasks().await.unwrap(); - assert_eq!(status.len(), 1); - } - #[meilisearch_test] async fn test_get_one_task(client: Client, index: Index) -> Result<(), Error> { let task = index @@ -1103,10 +1111,42 @@ mod tests { let status = index.get_task(task).await?; match status { - Task::Enqueued { content } => assert_eq!(content.index_uid, *index.uid), - Task::Processing { content } => assert_eq!(content.index_uid, *index.uid), - Task::Failed { content } => assert_eq!(content.task.index_uid, *index.uid), - Task::Succeeded { content } => assert_eq!(content.index_uid, *index.uid), + Task::Enqueued { + content: + EnqueuedTask { + index_uid: Some(index_uid), + .. + }, + } => assert_eq!(index_uid, *index.uid), + Task::Processing { + content: + EnqueuedTask { + index_uid: Some(index_uid), + .. + }, + } => assert_eq!(index_uid, *index.uid), + Task::Failed { + content: + FailedTask { + task: + SucceededTask { + index_uid: Some(index_uid), + .. + }, + .. + }, + } => assert_eq!(index_uid, *index.uid), + Task::Succeeded { + content: + SucceededTask { + index_uid: Some(index_uid), + .. + }, + } => assert_eq!(index_uid, *index.uid), + task => panic!( + "The task should have an index_uid that is not null {:?}", + task + ), } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 3b5417bc..411ddfbd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,7 +238,11 @@ mod request; pub mod search; /// Module containing [settings::Settings]. pub mod settings; +/// Module representing the [task_info::TaskInfo]s. +pub mod task_info; /// Module representing the [tasks::Task]s. pub mod tasks; /// Module that generates tenant tokens. mod tenant_tokens; +/// Module containing utilies functions. +mod utils; diff --git a/src/search.rs b/src/search.rs index 986d635f..200c8de2 100644 --- a/src/search.rs +++ b/src/search.rs @@ -112,7 +112,8 @@ type AttributeToCrop<'a> = (&'a str, Option); /// .with_query("space") /// .with_offset(42) /// .with_limit(21) -/// .build(); // you can also execute() instead of build() +/// +/// let res = query.execute().await?.unwrap(); /// ``` /// /// ``` diff --git a/src/settings.rs b/src/settings.rs index 254d17db..ac425635 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,7 +2,7 @@ use crate::{ errors::Error, indexes::Index, request::{request, Method}, - tasks::Task, + task_info::TaskInfo, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -478,8 +478,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn set_settings(&self, settings: &Settings) -> Result { - request::<&Settings, Task>( + pub async fn set_settings(&self, settings: &Settings) -> Result { + request::<&Settings, TaskInfo>( &format!("{}/indexes/{}/settings", self.client.host, self.uid), &self.client.api_key, Method::Post(settings), @@ -515,8 +515,8 @@ impl Index { pub async fn set_synonyms( &self, synonyms: &HashMap>, - ) -> Result { - request::<&HashMap>, Task>( + ) -> Result { + request::<&HashMap>, TaskInfo>( &format!( "{}/indexes/{}/settings/synonyms", self.client.host, self.uid @@ -551,8 +551,8 @@ impl Index { pub async fn set_stop_words( &self, stop_words: impl IntoIterator>, - ) -> Result { - request::, Task>( + ) -> Result { + request::, TaskInfo>( &format!( "{}/indexes/{}/settings/stop-words", self.client.host, self.uid @@ -601,8 +601,8 @@ impl Index { pub async fn set_ranking_rules( &self, ranking_rules: impl IntoIterator>, - ) -> Result { - request::, Task>( + ) -> Result { + request::, TaskInfo>( &format!( "{}/indexes/{}/settings/ranking-rules", self.client.host, self.uid @@ -642,8 +642,8 @@ impl Index { pub async fn set_filterable_attributes( &self, filterable_attributes: impl IntoIterator>, - ) -> Result { - request::, Task>( + ) -> Result { + request::, TaskInfo>( &format!( "{}/indexes/{}/settings/filterable-attributes", self.client.host, self.uid @@ -683,8 +683,8 @@ impl Index { pub async fn set_sortable_attributes( &self, sortable_attributes: impl IntoIterator>, - ) -> Result { - request::, Task>( + ) -> Result { + request::, TaskInfo>( &format!( "{}/indexes/{}/settings/sortable-attributes", self.client.host, self.uid @@ -723,8 +723,8 @@ impl Index { pub async fn set_distinct_attribute( &self, distinct_attribute: impl AsRef, - ) -> Result { - request::( + ) -> Result { + request::( &format!( "{}/indexes/{}/settings/distinct-attribute", self.client.host, self.uid @@ -758,8 +758,8 @@ impl Index { pub async fn set_searchable_attributes( &self, searchable_attributes: impl IntoIterator>, - ) -> Result { - request::, Task>( + ) -> Result { + request::, TaskInfo>( &format!( "{}/indexes/{}/settings/searchable-attributes", self.client.host, self.uid @@ -798,8 +798,8 @@ impl Index { pub async fn set_displayed_attributes( &self, displayed_attributes: impl IntoIterator>, - ) -> Result { - request::, Task>( + ) -> Result { + request::, TaskInfo>( &format!( "{}/indexes/{}/settings/displayed-attributes", self.client.host, self.uid @@ -836,8 +836,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_settings(&self) -> Result { - request::<(), Task>( + pub async fn reset_settings(&self) -> Result { + request::<(), TaskInfo>( &format!("{}/indexes/{}/settings", self.client.host, self.uid), &self.client.api_key, Method::Delete, @@ -865,8 +865,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_synonyms(&self) -> Result { - request::<(), Task>( + pub async fn reset_synonyms(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/synonyms", self.client.host, self.uid @@ -897,8 +897,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_stop_words(&self) -> Result { - request::<(), Task>( + pub async fn reset_stop_words(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/stop-words", self.client.host, self.uid @@ -930,8 +930,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_ranking_rules(&self) -> Result { - request::<(), Task>( + pub async fn reset_ranking_rules(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/ranking-rules", self.client.host, self.uid @@ -962,8 +962,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_filterable_attributes(&self) -> Result { - request::<(), Task>( + pub async fn reset_filterable_attributes(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/filterable-attributes", self.client.host, self.uid @@ -994,8 +994,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_sortable_attributes(&self) -> Result { - request::<(), Task>( + pub async fn reset_sortable_attributes(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/sortable-attributes", self.client.host, self.uid @@ -1026,8 +1026,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_distinct_attribute(&self) -> Result { - request::<(), Task>( + pub async fn reset_distinct_attribute(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/distinct-attribute", self.client.host, self.uid @@ -1058,8 +1058,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_searchable_attributes(&self) -> Result { - request::<(), Task>( + pub async fn reset_searchable_attributes(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/searchable-attributes", self.client.host, self.uid @@ -1090,8 +1090,8 @@ impl Index { /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn reset_displayed_attributes(&self) -> Result { - request::<(), Task>( + pub async fn reset_displayed_attributes(&self) -> Result { + request::<(), TaskInfo>( &format!( "{}/indexes/{}/settings/displayed-attributes", self.client.host, self.uid diff --git a/src/task_info.rs b/src/task_info.rs new file mode 100644 index 00000000..620c9429 --- /dev/null +++ b/src/task_info.rs @@ -0,0 +1,177 @@ +use serde::Deserialize; +use std::time::Duration; +use time::OffsetDateTime; + +use crate::{client::Client, errors::Error, tasks::*}; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskInfo { + #[serde(with = "time::serde::rfc3339")] + pub enqueued_at: OffsetDateTime, + pub index_uid: Option, + pub status: String, + #[serde(flatten)] + pub update_type: TaskType, + pub task_uid: u32, +} + +impl AsRef for TaskInfo { + fn as_ref(&self) -> &u32 { + &self.task_uid + } +} + +impl TaskInfo { + pub fn get_task_uid(&self) -> u32 { + self.task_uid + } + + /// Wait until Meilisearch processes a task provided by [TaskInfo], and get its status. + /// + /// `interval` = The frequency at which the server should be polled. Default = 50ms + /// `timeout` = The maximum time to wait for processing to complete. Default = 5000ms + /// + /// If the waited time exceeds `timeout` then an [Error::Timeout] will be returned. + /// + /// See also [Client::wait_for_task, Index::wait_for_task]. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, tasks::Task, task_info::TaskInfo}; + /// # use serde::{Serialize, Deserialize}; + /// # + /// # #[derive(Debug, Serialize, Deserialize, PartialEq)] + /// # struct Document { + /// # id: usize, + /// # value: String, + /// # kind: String, + /// # } + /// # + /// # + /// # futures::executor::block_on(async move { + /// let client = Client::new("http://localhost:7700", "masterKey"); + /// let movies = client.index("movies_wait_for_completion"); + /// + /// let status = movies.add_documents(&[ + /// Document { id: 0, kind: "title".into(), value: "The Social Network".to_string() }, + /// Document { id: 1, kind: "title".into(), value: "Harry Potter and the Sorcerer's Stone".to_string() }, + /// ], None) + /// .await + /// .unwrap() + /// .wait_for_completion(&client, None, None) + /// .await + /// .unwrap(); + /// + /// assert!(matches!(status, Task::Succeeded { .. })); + /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn wait_for_completion( + self, + client: &Client, + interval: Option, + timeout: Option, + ) -> Result { + client.wait_for_task(self, interval, timeout).await + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + client::*, + errors::{ErrorCode, ErrorType}, + indexes::Index, + }; + use meilisearch_test_macro::meilisearch_test; + use serde::{Deserialize, Serialize}; + use std::time::Duration; + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Document { + id: usize, + value: String, + kind: String, + } + + #[test] + fn test_deserialize_task_info() { + let datetime = OffsetDateTime::parse( + "2022-02-03T13:02:38.369634Z", + &::time::format_description::well_known::Rfc3339, + ) + .unwrap(); + + let task_info: TaskInfo = serde_json::from_str( + r#" +{ + "enqueuedAt": "2022-02-03T13:02:38.369634Z", + "indexUid": "mieli", + "status": "enqueued", + "type": "documentAdditionOrUpdate", + "taskUid": 12 +}"#, + ) + .unwrap(); + + assert!(matches!( + task_info, + TaskInfo { + enqueued_at, + index_uid: Some(index_uid), + task_uid: 12, + update_type: TaskType::DocumentAdditionOrUpdate { details: None }, + status, + } + if enqueued_at == datetime && index_uid == "mieli" && status == "enqueued")); + } + + #[meilisearch_test] + async fn test_wait_for_task_with_args(client: Client, movies: Index) -> Result<(), Error> { + let task_info = movies + .add_documents( + &[ + Document { + id: 0, + kind: "title".into(), + value: "The Social Network".to_string(), + }, + Document { + id: 1, + kind: "title".into(), + value: "Harry Potter and the Sorcerer's Stone".to_string(), + }, + ], + None, + ) + .await?; + + let task = client + .get_task(task_info) + .await? + .wait_for_completion( + &client, + Some(Duration::from_millis(1)), + Some(Duration::from_millis(6000)), + ) + .await?; + + assert!(matches!(task, Task::Succeeded { .. })); + Ok(()) + } + + #[meilisearch_test] + // TODO: failing because settings routes now uses PUT instead of POST as http method + async fn test_failing_task(client: Client, movies: Index) -> Result<(), Error> { + let task_info = movies.set_ranking_rules(["wrong_ranking_rule"]).await?; + let task = client.wait_for_task(task_info, None, None).await?; + + let error = task.unwrap_failure(); + assert_eq!(error.error_code, ErrorCode::InvalidRankingRule); + assert_eq!(error.error_type, ErrorType::InvalidRequest); + Ok(()) + } +} diff --git a/src/tasks.rs b/src/tasks.rs index 050b1f83..6467115c 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize}; use std::time::Duration; use time::OffsetDateTime; @@ -9,20 +9,38 @@ use crate::{ #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum TaskType { - ClearAll, Customs, - DocumentAddition { details: Option }, - DocumentPartial { details: Option }, - DocumentDeletion { details: Option }, - IndexCreation { details: Option }, - IndexUpdate { details: Option }, - IndexDeletion { details: Option }, - SettingsUpdate { details: Option }, + DocumentAdditionOrUpdate { + details: Option, + }, + DocumentDeletion { + details: Option, + }, + IndexCreation { + details: Option, + }, + IndexUpdate { + details: Option, + }, + IndexDeletion { + details: Option, + }, + SettingsUpdate { + details: Option, + }, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TasksResults { + pub results: Vec, + pub limit: u32, + pub from: Option, + pub next: Option, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DocumentAddition { +pub struct DocumentAdditionOrUpdate { pub indexed_documents: Option, pub received_documents: usize, } @@ -56,11 +74,11 @@ pub struct IndexDeletion { pub struct FailedTask { pub error: MeilisearchError, #[serde(flatten)] - pub task: ProcessedTask, + pub task: SucceededTask, } -impl AsRef for FailedTask { - fn as_ref(&self) -> &u64 { +impl AsRef for FailedTask { + fn as_ref(&self) -> &u32 { &self.task.uid } } @@ -76,7 +94,7 @@ where #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -pub struct ProcessedTask { +pub struct SucceededTask { #[serde(deserialize_with = "deserialize_duration")] pub duration: Duration, #[serde(with = "time::serde::rfc3339")] @@ -85,14 +103,14 @@ pub struct ProcessedTask { pub started_at: OffsetDateTime, #[serde(with = "time::serde::rfc3339")] pub finished_at: OffsetDateTime, - pub index_uid: String, + pub index_uid: Option, #[serde(flatten)] pub update_type: TaskType, - pub uid: u64, + pub uid: u32, } -impl AsRef for ProcessedTask { - fn as_ref(&self) -> &u64 { +impl AsRef for SucceededTask { + fn as_ref(&self) -> &u32 { &self.uid } } @@ -102,14 +120,14 @@ impl AsRef for ProcessedTask { pub struct EnqueuedTask { #[serde(with = "time::serde::rfc3339")] pub enqueued_at: OffsetDateTime, - pub index_uid: String, + pub index_uid: Option, #[serde(flatten)] pub update_type: TaskType, - pub uid: u64, + pub uid: u32, } -impl AsRef for EnqueuedTask { - fn as_ref(&self) -> &u64 { +impl AsRef for EnqueuedTask { + fn as_ref(&self) -> &u32 { &self.uid } } @@ -131,12 +149,12 @@ pub enum Task { }, Succeeded { #[serde(flatten)] - content: ProcessedTask, + content: SucceededTask, }, } impl Task { - pub fn get_uid(&self) -> u64 { + pub fn get_uid(&self) -> u32 { match self { Self::Enqueued { content } | Self::Processing { content } => *content.as_ref(), Self::Failed { content } => *content.as_ref(), @@ -225,12 +243,12 @@ impl Task { match self { Self::Succeeded { content: - ProcessedTask { + SucceededTask { index_uid, update_type: TaskType::IndexCreation { .. }, .. }, - } => Ok(client.index(index_uid)), + } => Ok(client.index(index_uid.unwrap())), _ => Err(self), } } @@ -252,7 +270,7 @@ impl Task { /// # let task = client.create_index("unwrap_failure", None).await.unwrap(); /// # let index = client.wait_for_task(task, None, None).await.unwrap().try_make_index(&client).unwrap(); /// - /// + /// // TODO: fails until http method are implemented /// let task = index.set_ranking_rules(["wrong_ranking_rule"]) /// .await /// .unwrap() @@ -303,6 +321,7 @@ impl Task { /// assert!(task.is_failure()); /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); + /// ``` pub fn is_failure(&self) -> bool { matches!(self, Self::Failed { .. }) } @@ -330,6 +349,7 @@ impl Task { /// assert!(task.is_success()); /// # task.try_make_index(&client).unwrap().delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); + /// ``` pub fn is_success(&self) -> bool { matches!(self, Self::Succeeded { .. }) } @@ -337,8 +357,9 @@ impl Task { /// Returns `true` if the [Task] is pending ([Self::Enqueued] or [Self::Processing]). /// /// # Example - /// - /// ``` + /// ```no_run + /// # // The test is not run because it checks for an enqueued or processed status + /// # // and the task might already be processed when checking the status after the get_task call /// # use meilisearch_sdk::{client::*, indexes::*, errors::ErrorCode}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); @@ -346,21 +367,22 @@ impl Task { /// # /// # futures::executor::block_on(async move { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let task = client + /// let task_info = client /// .create_index("is_pending", None) /// .await /// .unwrap(); - /// + /// let task = client.get_task(task_info).await.unwrap(); /// assert!(task.is_pending()); /// # task.wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap().delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); + /// ``` pub fn is_pending(&self) -> bool { matches!(self, Self::Enqueued { .. } | Self::Processing { .. }) } } -impl AsRef for Task { - fn as_ref(&self) -> &u64 { +impl AsRef for Task { + fn as_ref(&self) -> &u32 { match self { Self::Enqueued { content } | Self::Processing { content } => content.as_ref(), Self::Succeeded { content } => content.as_ref(), @@ -369,32 +391,73 @@ impl AsRef for Task { } } -#[cfg(not(target_arch = "wasm32"))] -pub(crate) async fn async_sleep(interval: Duration) { - let (sender, receiver) = futures::channel::oneshot::channel::<()>(); - std::thread::spawn(move || { - std::thread::sleep(interval); - let _ = sender.send(()); - }); - let _ = receiver.await; +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct TasksQuery<'a> { + #[serde(skip_serializing)] + pub client: &'a Client, + // Index uids array to only retrieve the tasks of the indexes. + #[serde(skip_serializing_if = "Option::is_none")] + pub index_uid: Option>, + // Statuses array to only retrieve the tasks with these statuses. + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option>, + // Types array to only retrieve the tasks with these [TaskType]. + #[serde(skip_serializing_if = "Option::is_none", rename = "type")] + pub task_type: Option>, + // Maximum number of tasks to return + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + // The first task uid that should be returned + #[serde(skip_serializing_if = "Option::is_none")] + pub from: Option, } -#[cfg(target_arch = "wasm32")] -pub(crate) async fn async_sleep(interval: Duration) { - use std::convert::TryInto; - use wasm_bindgen_futures::JsFuture; - - JsFuture::from(js_sys::Promise::new(&mut |yes, _| { - web_sys::window() - .unwrap() - .set_timeout_with_callback_and_timeout_and_arguments_0( - &yes, - interval.as_millis().try_into().unwrap(), - ) - .unwrap(); - })) - .await - .unwrap(); +#[allow(missing_docs)] +impl<'a> TasksQuery<'a> { + pub fn new(client: &'a Client) -> TasksQuery<'a> { + TasksQuery { + client, + index_uid: None, + status: None, + task_type: None, + limit: None, + from: None, + } + } + pub fn with_index_uid<'b>( + &'b mut self, + index_uid: impl IntoIterator, + ) -> &'b mut TasksQuery<'a> { + self.index_uid = Some(index_uid.into_iter().collect()); + self + } + pub fn with_status<'b>( + &'b mut self, + status: impl IntoIterator, + ) -> &'b mut TasksQuery<'a> { + self.status = Some(status.into_iter().collect()); + self + } + pub fn with_type<'b>( + &'b mut self, + task_type: impl IntoIterator, + ) -> &'b mut TasksQuery<'a> { + self.task_type = Some(task_type.into_iter().collect()); + self + } + pub fn with_limit<'b>(&'b mut self, limit: u32) -> &'b mut TasksQuery<'a> { + self.limit = Some(limit); + self + } + pub fn with_from<'b>(&'b mut self, from: u32) -> &'b mut TasksQuery<'a> { + self.from = Some(from); + self + } + + pub async fn execute(&'a self) -> Result { + self.client.get_tasks_with(self).await + } } #[cfg(test)] @@ -405,8 +468,9 @@ mod test { errors::{ErrorCode, ErrorType}, }; use meilisearch_test_macro::meilisearch_test; + use mockito::mock; use serde::{Deserialize, Serialize}; - use std::time::{self, Duration}; + use std::time::Duration; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Document { @@ -429,7 +493,7 @@ mod test { "enqueuedAt": "2022-02-03T13:02:38.369634Z", "indexUid": "mieli", "status": "enqueued", - "type": "documentAddition", + "type": "documentAdditionOrUpdate", "uid": 12 }"#, ) @@ -440,8 +504,8 @@ mod test { Task::Enqueued { content: EnqueuedTask { enqueued_at, - index_uid, - update_type: TaskType::DocumentAddition { details: None }, + index_uid: Some(index_uid), + update_type: TaskType::DocumentAdditionOrUpdate { details: None }, uid: 12, } } @@ -460,7 +524,7 @@ mod test { "indexUid": "mieli", "startedAt": "2022-02-03T15:17:02.812338Z", "status": "processing", - "type": "documentAddition", + "type": "documentAdditionOrUpdate", "uid": 14 }"#, ) @@ -470,8 +534,8 @@ mod test { task, Task::Processing { content: EnqueuedTask { - update_type: TaskType::DocumentAddition { - details: Some(DocumentAddition { + update_type: TaskType::DocumentAdditionOrUpdate { + details: Some(DocumentAdditionOrUpdate { received_documents: 19547, indexed_documents: None, }) @@ -495,7 +559,7 @@ mod test { "indexUid": "mieli", "startedAt": "2022-02-03T15:17:02.812338Z", "status": "succeeded", - "type": "documentAddition", + "type": "documentAdditionOrUpdate", "uid": 14 }"#, ) @@ -504,9 +568,9 @@ mod test { assert!(matches!( task, Task::Succeeded { - content: ProcessedTask { - update_type: TaskType::DocumentAddition { - details: Some(DocumentAddition { + content: SucceededTask { + update_type: TaskType::DocumentAdditionOrUpdate { + details: Some(DocumentAdditionOrUpdate { received_documents: 19547, indexed_documents: Some(19546), }) @@ -521,11 +585,8 @@ mod test { } #[meilisearch_test] - async fn test_wait_for_pending_updates_with_args( - client: Client, - movies: Index, - ) -> Result<(), Error> { - let status = movies + async fn test_wait_for_task_with_args(client: Client, movies: Index) -> Result<(), Error> { + let task = movies .add_documents( &[ Document { @@ -549,62 +610,98 @@ mod test { ) .await?; - assert!(matches!(status, Task::Succeeded { .. })); + assert!(matches!(task, Task::Succeeded { .. })); Ok(()) } #[meilisearch_test] - async fn test_wait_for_pending_updates_time_out( - client: Client, - movies: Index, - ) -> Result<(), Error> { - let task = movies - .add_documents( - &[ - Document { - id: 0, - kind: "title".into(), - value: "The Social Network".to_string(), - }, - Document { - id: 1, - kind: "title".into(), - value: "Harry Potter and the Sorcerer's Stone".to_string(), - }, - ], - None, - ) - .await?; + async fn test_get_tasks_no_params() -> Result<(), Error> { + let mock_server_url = &mockito::server_url(); + let client = Client::new(mock_server_url, "masterKey"); + let path = "/tasks"; - let error = client - .wait_for_task( - task, - Some(Duration::from_millis(1)), - Some(Duration::from_nanos(1)), - ) - .await - .unwrap_err(); + let mock_res = mock("GET", path).with_status(200).create(); + let _ = client.get_tasks().await; + mock_res.assert(); - assert!(matches!(error, Error::Timeout)); Ok(()) } #[meilisearch_test] - async fn test_async_sleep() { - let sleep_duration = time::Duration::from_millis(10); - let now = time::Instant::now(); + async fn test_get_tasks_with_params() -> Result<(), Error> { + let mock_server_url = &mockito::server_url(); + let client = Client::new(mock_server_url, "masterKey"); + let path = + "/tasks?indexUid=movies,test&status=equeued&type=documentDeletion&limit=0&from=1"; + + let mock_res = mock("GET", path).with_status(200).create(); - async_sleep(sleep_duration).await; + let mut query = TasksQuery::new(&client); + query + .with_index_uid(["movies", "test"]) + .with_status(["equeued"]) + .with_type(["documentDeletion"]) + .with_from(1) + .with_limit(0); - assert!(now.elapsed() >= sleep_duration); + let _ = client.get_tasks_with(&query).await; + + mock_res.assert(); + Ok(()) } #[meilisearch_test] - async fn test_failing_update(client: Client, movies: Index) -> Result<(), Error> { - let task = movies.set_ranking_rules(["wrong_ranking_rule"]).await?; - let status = client.wait_for_task(task, None, None).await?; + async fn test_get_tasks_on_struct_with_params() -> Result<(), Error> { + let mock_server_url = &mockito::server_url(); + let client = Client::new(mock_server_url, "masterKey"); + let path = "/tasks?indexUid=movies,test&status=equeued&type=documentDeletion"; + + let mock_res = mock("GET", path).with_status(200).create(); + + let mut query = TasksQuery::new(&client); + let _ = query + .with_index_uid(["movies", "test"]) + .with_status(["equeued"]) + .with_type(["documentDeletion"]) + .execute() + .await; + + // let _ = client.get_tasks(&query).await; + mock_res.assert(); + Ok(()) + } + + #[meilisearch_test] + async fn test_get_tasks_with_none_existant_index_uid(client: Client) -> Result<(), Error> { + let mut query = TasksQuery::new(&client); + query.with_index_uid(["no_name"]); + let tasks = client.get_tasks_with(&query).await.unwrap(); + + assert_eq!(tasks.results.len(), 0); + Ok(()) + } + + #[meilisearch_test] + async fn test_get_tasks_with_execute(client: Client) -> Result<(), Error> { + let tasks = TasksQuery::new(&client) + .with_index_uid(["no_name"]) + .execute() + .await + .unwrap(); + + assert_eq!(tasks.results.len(), 0); + Ok(()) + } + + #[meilisearch_test] + // TODO: failing because settings routes now uses PUT instead of POST as http method + async fn test_failing_task(client: Client, movies: Index) -> Result<(), Error> { + let task_info = movies.set_ranking_rules(["wrong_ranking_rule"]).await?; + + let task = client.get_task(task_info).await?; + let task = client.wait_for_task(task, None, None).await?; - let error = status.unwrap_failure(); + let error = task.unwrap_failure(); assert_eq!(error.error_code, ErrorCode::InvalidRankingRule); assert_eq!(error.error_type, ErrorType::InvalidRequest); Ok(()) diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..fd7b3d9d --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,45 @@ +use std::time::Duration; + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) async fn async_sleep(interval: Duration) { + let (sender, receiver) = futures::channel::oneshot::channel::<()>(); + std::thread::spawn(move || { + std::thread::sleep(interval); + let _ = sender.send(()); + }); + let _ = receiver.await; +} + +#[cfg(target_arch = "wasm32")] +pub(crate) async fn async_sleep(interval: Duration) { + use std::convert::TryInto; + use wasm_bindgen_futures::JsFuture; + + JsFuture::from(js_sys::Promise::new(&mut |yes, _| { + web_sys::window() + .unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0( + &yes, + interval.as_millis().try_into().unwrap(), + ) + .unwrap(); + })) + .await + .unwrap(); +} + +#[cfg(test)] +mod test { + use super::*; + use meilisearch_test_macro::meilisearch_test; + + #[meilisearch_test] + async fn test_async_sleep() { + let sleep_duration = std::time::Duration::from_millis(10); + let now = time::Instant::now(); + + async_sleep(sleep_duration).await; + + assert!(now.elapsed() >= sleep_duration); + } +} From 99f9e392e0a7ccd92bd2065b486a54e5fec7b6c1 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:48:38 +0200 Subject: [PATCH 04/19] Update HTTP methods for v0.28.0 (#312) * Update task api v0.28.0 * Update tests to be relevant with the structures where they are ran * Update types in code-samples * Remove clearAll task type * Add index_uid as an optional in the Tasks * Add comments on best builder implementation * Add execute function on get_tasks * Update code samples * Remove out of context comment * Fix tests * Fix pagination tests * Update HTTP methods for v0.28.0 * Fix clippy * Remove comment of task tests since the tests are now sucesful * Fix doc tests * Fix get tasks inside index structure * Fix none doc tests with new get_tasks api * Add from and limit in tasks params * Add warning on failing test * Update task API * Remove useless comment * Rename type in Task structure * Removed useless newlines * Fix typo in comment * Add missing semi-column * Fix clippy errors --- src/dumps.rs | 2 +- src/indexes.rs | 2 +- src/search.rs | 2 +- src/settings.rs | 18 +++++++++--------- src/task_info.rs | 1 - src/tasks.rs | 1 - 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/dumps.rs b/src/dumps.rs index 74206a9f..28ff66b8 100644 --- a/src/dumps.rs +++ b/src/dumps.rs @@ -43,7 +43,7 @@ use serde::Deserialize; /// The status of a dump.\ /// Contained in [`DumpInfo`]. -#[derive(Debug, Deserialize, Clone, PartialEq)] +#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] #[serde(rename_all = "snake_case")] pub enum DumpStatus { /// Dump creation is in progress. diff --git a/src/indexes.rs b/src/indexes.rs index 54294f69..067a9ab1 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -97,7 +97,7 @@ impl Index { request::( &format!("{}/indexes/{}", self.client.host, self.uid), &self.client.api_key, - Method::Put(json!({ "primaryKey": primary_key.as_ref() })), + Method::Patch(json!({ "primaryKey": primary_key.as_ref() })), 200, ) .await?; diff --git a/src/search.rs b/src/search.rs index 200c8de2..954bedaf 100644 --- a/src/search.rs +++ b/src/search.rs @@ -3,7 +3,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use serde_json::{Map, Value}; use std::collections::HashMap; -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Deserialize, Debug, Eq, PartialEq)] pub struct MatchRange { pub start: usize, pub length: usize, diff --git a/src/settings.rs b/src/settings.rs index ac425635..e2f33da9 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -482,7 +482,7 @@ impl Index { request::<&Settings, TaskInfo>( &format!("{}/indexes/{}/settings", self.client.host, self.uid), &self.client.api_key, - Method::Post(settings), + Method::Patch(settings), 202, ) .await @@ -522,7 +522,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post(synonyms), + Method::Put(synonyms), 202, ) .await @@ -558,7 +558,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post( + Method::Put( stop_words .into_iter() .map(|v| v.as_ref().to_string()) @@ -608,7 +608,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post( + Method::Put( ranking_rules .into_iter() .map(|v| v.as_ref().to_string()) @@ -649,7 +649,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post( + Method::Put( filterable_attributes .into_iter() .map(|v| v.as_ref().to_string()) @@ -690,7 +690,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post( + Method::Put( sortable_attributes .into_iter() .map(|v| v.as_ref().to_string()) @@ -730,7 +730,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post(distinct_attribute.as_ref().to_string()), + Method::Put(distinct_attribute.as_ref().to_string()), 202, ) .await @@ -765,7 +765,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post( + Method::Put( searchable_attributes .into_iter() .map(|v| v.as_ref().to_string()) @@ -805,7 +805,7 @@ impl Index { self.client.host, self.uid ), &self.client.api_key, - Method::Post( + Method::Put( displayed_attributes .into_iter() .map(|v| v.as_ref().to_string()) diff --git a/src/task_info.rs b/src/task_info.rs index 620c9429..cb970c7d 100644 --- a/src/task_info.rs +++ b/src/task_info.rs @@ -164,7 +164,6 @@ mod test { } #[meilisearch_test] - // TODO: failing because settings routes now uses PUT instead of POST as http method async fn test_failing_task(client: Client, movies: Index) -> Result<(), Error> { let task_info = movies.set_ranking_rules(["wrong_ranking_rule"]).await?; let task = client.wait_for_task(task_info, None, None).await?; diff --git a/src/tasks.rs b/src/tasks.rs index 6467115c..26c470bf 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -694,7 +694,6 @@ mod test { } #[meilisearch_test] - // TODO: failing because settings routes now uses PUT instead of POST as http method async fn test_failing_task(client: Client, movies: Index) -> Result<(), Error> { let task_info = movies.set_ranking_rules(["wrong_ranking_rule"]).await?; From 24e6b173673cd66bfffbe847b805cc0aef4a0ad1 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:20:43 +0200 Subject: [PATCH 05/19] Update keys for v0.28.0 (#313) * Update task api v0.28.0 * Update tests to be relevant with the structures where they are ran * Update types in code-samples * Remove clearAll task type * Add index_uid as an optional in the Tasks * Add comments on best builder implementation * Add execute function on get_tasks * Update code samples * Remove out of context comment * Fix tests * Fix pagination tests * Update HTTP methods for v0.28.0 * Fix clippy * Remove comment of task tests since the tests are now sucesful * Fix doc tests * Update keys for v0.28.0 * Fix get tasks inside index structure * Make description and name optional in keys * Fix none doc tests with new get_tasks api * Add from and limit in tasks params * Add warning on failing test * Update keys design * Update task API * Remove useless comment * Remove client as mandatory parameter for the keyUpdater * Add doc and tests on doc * Fix docs tests on keys in client * Fix docs tests * Remove dbg * Add with_uid filter * Add new actions on key creation * Remove new line at the start of docs * Fix clippy errors * Rename type in Task structure * Removed useless newlines * Fix typo in comment * Add missing semi-column * Improve doc comments * Fix Keys actions * Un hide assert in execute fonction of key * Remove useless comment * Remove flacky test * Use name parameter instead of hardcoded name in key tests * Update src/key.rs Co-authored-by: Tamo Co-authored-by: Tamo --- src/client.rs | 231 ++++++++++-------------- src/key.rs | 486 ++++++++++++++++++++++++++++++++++++++------------ src/search.rs | 4 +- 3 files changed, 473 insertions(+), 248 deletions(-) diff --git a/src/client.rs b/src/client.rs index 450bb583..bd1e2812 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ use crate::{ errors::*, indexes::*, - key::{Key, KeyBuilder}, + key::{Key, KeyBuilder, KeyUpdater, KeysQuery, KeysResults}, request::*, task_info::TaskInfo, tasks::{Task, TasksQuery, TasksResults}, @@ -309,6 +309,40 @@ impl Client { } } + /// Get the API [Key]s from Meilisearch with parameters. + /// See the [meilisearch documentation](https://docs.meilisearch.com/reference/api/keys.html#get-all-keys). + /// + /// See also [Client::create_key] and [Client::get_key]. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, errors::Error, key::KeysQuery}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut query = KeysQuery::new(); + /// query.with_limit(1); + /// let keys = client.get_keys_with(&query).await.unwrap(); + /// + /// assert_eq!(keys.results.len(), 1); + /// # }); + /// ``` + pub async fn get_keys_with(&self, keys_query: &KeysQuery) -> Result { + let keys = request::<&KeysQuery, KeysResults>( + &format!("{}/keys", self.host), + &self.api_key, + Method::Get(keys_query), + 200, + ) + .await?; + + Ok(keys) + } + /// Get the API [Key]s from Meilisearch. /// See the [meilisearch documentation](https://docs.meilisearch.com/reference/api/keys.html#get-all-keys). /// @@ -325,18 +359,12 @@ impl Client { /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// let keys = client.get_keys().await.unwrap(); - /// assert!(keys.len() >= 2); + /// + /// assert_eq!(keys.results.len(), 2); /// # }); /// ``` - pub async fn get_keys(&self) -> Result, Error> { - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Keys { - #[serde(rename = "results")] - pub inner: Vec, - } - - let keys = request::<(), Keys>( + pub async fn get_keys(&self) -> Result { + let keys = request::<(), KeysResults>( &format!("{}/keys", self.host), &self.api_key, Method::Get(()), @@ -344,7 +372,7 @@ impl Client { ) .await?; - Ok(keys.inner) + Ok(keys) } /// Get one API [Key] from Meilisearch. @@ -362,11 +390,12 @@ impl Client { /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// # let key = client.get_keys().await.unwrap().into_iter().find(|k| k.description.starts_with("Default Search API Key")).unwrap(); - /// let key_id = // enter your API key here, for the example we'll say we entered our search API key. - /// # key.key; + /// # let key = client.get_keys().await.unwrap().results.into_iter() + /// .find(|k| k.name.as_ref().map_or(false, |name| name.starts_with("Default Search API Key"))); + /// let key_id = key.unwrap().key // enter your API key here, for the example we use the search API key. /// let key = client.get_key(key_id).await.unwrap(); - /// assert_eq!(key.description, "Default Search API Key (Use it to search from the frontend)"); + /// + /// assert_eq!(key.name, Some("Default Search API Key".to_string())); /// # }); /// ``` pub async fn get_key(&self, key: impl AsRef) -> Result { @@ -394,14 +423,14 @@ impl Client { /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let key = KeyBuilder::new("delete_key"); + /// let key = KeyBuilder::new(); /// let key = client.create_key(key).await.unwrap(); /// let inner_key = key.key.clone(); /// /// client.delete_key(key).await.unwrap(); /// /// let keys = client.get_keys().await.unwrap(); - /// assert!(keys.iter().all(|key| key.key != inner_key)); + /// assert!(keys.results.iter().all(|key| key.key != inner_key)); /// # }); /// ``` pub async fn delete_key(&self, key: impl AsRef) -> Result<(), Error> { @@ -429,11 +458,12 @@ impl Client { /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let mut key = KeyBuilder::new("create_key"); - /// key.with_index("*").with_action(Action::DocumentsAdd); - /// let key = client.create_key(key).await.unwrap(); + /// let name = "create_key".to_string(); + /// let mut key = KeyBuilder::new(); + /// key.with_name(&name); /// - /// assert_eq!(key.description, "create_key"); + /// let key = client.create_key(key).await.unwrap(); + /// assert_eq!(key.name, Some(name)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` @@ -455,25 +485,26 @@ impl Client { /// # Example /// /// ``` - /// # use meilisearch_sdk::{client::*, errors::Error, key::KeyBuilder}; + /// # use meilisearch_sdk::{client::*, errors::Error, key::KeyBuilder, key::KeyUpdater}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let key = KeyBuilder::new("update_key"); - /// let mut key = client.create_key(key).await.unwrap(); - /// assert!(key.indexes.is_empty()); + /// let new_key = KeyBuilder::new(); + /// let name = "my name".to_string(); + /// let mut new_key = client.create_key(new_key).await.unwrap(); + /// let mut key_update = KeyUpdater::new(new_key); + /// key_update.with_name(&name); /// - /// key.indexes = vec!["*".to_string()]; - /// let key = client.update_key(key).await.unwrap(); - /// assert_eq!(key.indexes, vec!["*"]); + /// let key = client.update_key(key_update).await.unwrap(); + /// assert_eq!(key.name, Some(name)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` - pub async fn update_key(&self, key: impl AsRef) -> Result { - request::<&Key, Key>( + pub async fn update_key(&self, key: impl AsRef) -> Result { + request::<&KeyUpdater, Key>( &format!("{}/keys/{}", self.host, key.as_ref().key), &self.api_key, Method::Patch(key.as_ref()), @@ -814,33 +845,35 @@ mod tests { async fn test_get_tasks_with_params(client: Client) { let query = TasksQuery::new(&client); let tasks = client.get_tasks_with(&query).await.unwrap(); + assert!(tasks.results.len() >= 2); } #[meilisearch_test] async fn test_get_keys(client: Client) { let keys = client.get_keys().await.unwrap(); - assert!(keys.len() >= 2); - assert!(keys.iter().any( - |k| k.description != "Default Search API Key (Use it to search from the frontend)" - )); - assert!(keys.iter().any( - |k| k.description != "Default Admin API Key (Use it for all other operations. Caution! Do not use it on a public frontend)" - )); + + assert!(keys.results.len() >= 2); } #[meilisearch_test] - async fn test_delete_key(client: Client, description: String) { - let key = KeyBuilder::new(description); + async fn test_delete_key(client: Client, name: String) { + let mut key = KeyBuilder::new(); + key.with_name(&name); let key = client.create_key(key).await.unwrap(); client.delete_key(&key).await.unwrap(); - let keys = client.get_keys().await.unwrap(); - assert!(keys.iter().all(|k| k.key != key.key)); + let keys = KeysQuery::new() + .with_limit(10000) + .execute(&client) + .await + .unwrap(); + + assert!(keys.results.iter().all(|k| k.key != key.key)); } #[meilisearch_test] - async fn test_error_delete_key(mut client: Client, description: String) { + async fn test_error_delete_key(mut client: Client, name: String) { // ==> accessing a key that does not exist let error = client.delete_key("invalid_key").await.unwrap_err(); assert!(matches!( @@ -853,7 +886,8 @@ mod tests { )); // ==> executing the action without enough right - let key = KeyBuilder::new(description); + let mut key = KeyBuilder::new(); + key.with_name(&name); let key = client.create_key(key).await.unwrap(); let master_key = client.api_key.clone(); @@ -886,16 +920,18 @@ mod tests { } #[meilisearch_test] - async fn test_create_key(client: Client, description: String) { + async fn test_create_key(client: Client, name: String) { let expires_at = OffsetDateTime::now_utc() + time::Duration::HOUR; - let mut key = KeyBuilder::new(description.clone()); + let mut key = KeyBuilder::new(); key.with_action(Action::DocumentsAdd) + .with_name(&name) .with_expires_at(expires_at.clone()) + .with_description("a description") .with_index("*"); let key = client.create_key(key).await.unwrap(); assert_eq!(key.actions, vec![Action::DocumentsAdd]); - assert_eq!(key.description, description); + assert_eq!(&key.name, &Some(name)); // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats assert_eq!( key.expires_at.unwrap().unix_timestamp(), @@ -903,27 +939,14 @@ mod tests { ); assert_eq!(key.indexes, vec!["*".to_string()]); - let keys = client.get_keys().await.unwrap(); - - let remote_key = keys.iter().find(|k| k.key == key.key).unwrap(); - - assert_eq!(remote_key.actions, vec![Action::DocumentsAdd]); - assert_eq!(remote_key.description, description); - // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats - assert_eq!( - remote_key.expires_at.unwrap().unix_timestamp(), - expires_at.unix_timestamp() - ); - assert_eq!(remote_key.indexes, vec!["*".to_string()]); - client.delete_key(key).await.unwrap(); } #[meilisearch_test] - async fn test_error_create_key(mut client: Client, description: String) { + async fn test_error_create_key(mut client: Client, name: String) { // ==> Invalid index name /* TODO: uncomment once meilisearch fix this bug: https://github.com/meilisearch/meilisearch/issues/2158 - let mut key = KeyBuilder::new(&description); + let mut key = KeyBuilder::new(); key.with_index("invalid index # / \\name with spaces"); let error = client.create_key(key).await.unwrap_err(); @@ -938,14 +961,16 @@ mod tests { */ // ==> executing the action without enough right - let no_right_key = KeyBuilder::new(&description); + let mut no_right_key = KeyBuilder::new(); + no_right_key.with_name(&format!("{name}_1")); let no_right_key = client.create_key(no_right_key).await.unwrap(); // backup the master key for cleanup at the end of the test let master_client = client.clone(); client.api_key = Arc::new(no_right_key.key.clone()); - let key = KeyBuilder::new(&description); + let mut key = KeyBuilder::new(); + key.with_name(format!("{name}_2")); let error = client.create_key(key).await.unwrap_err(); assert!(matches!( @@ -963,84 +988,22 @@ mod tests { #[meilisearch_test] async fn test_update_key(client: Client, description: String) { - let expires_at = OffsetDateTime::now_utc() + time::Duration::HOUR; - let key = KeyBuilder::new(description.clone()); + let mut key = KeyBuilder::new(); + key.with_name("test_update_key"); let mut key = client.create_key(key).await.unwrap(); - key.actions = vec![Action::DocumentsAdd]; - key.expires_at = Some(expires_at); - key.indexes = vec!["*".to_string()]; - - let key = client.update_key(key).await.unwrap(); - - assert_eq!(key.actions, vec![Action::DocumentsAdd]); - assert_eq!(key.description, description); - // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats - assert_eq!( - key.expires_at.unwrap().unix_timestamp(), - expires_at.unix_timestamp() - ); - assert_eq!(key.indexes, vec!["*".to_string()]); + let name = "new name".to_string(); + key.with_description(&description); + key.with_name(&name); - let keys = client.get_keys().await.unwrap(); - - let remote_key = keys.iter().find(|k| k.key == key.key).unwrap(); + let key = key.update(&client).await.unwrap(); - assert_eq!(remote_key.actions, vec![Action::DocumentsAdd]); - assert_eq!(remote_key.description, description); - // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats - assert_eq!( - remote_key.expires_at.unwrap().unix_timestamp(), - expires_at.unix_timestamp() - ); - assert_eq!(remote_key.indexes, vec!["*".to_string()]); + assert_eq!(key.description, Some(description)); + assert_eq!(key.name, Some(name)); client.delete_key(key).await.unwrap(); } - #[meilisearch_test] - async fn test_error_update_key(mut client: Client, description: String) { - let key = KeyBuilder::new(description.clone()); - let key = client.create_key(key).await.unwrap(); - - // ==> Invalid index name - /* TODO: uncomment once meilisearch fix this bug: https://github.com/meilisearch/meilisearch/issues/2158 - key.indexes = vec!["invalid index # / \\name with spaces".to_string()]; - let error = client.update_key(key).await.unwrap_err(); - - assert!(matches!( - error, - Error::MeilisearchError { - error_code: ErrorCode::InvalidApiKeyIndexes, - error_type: ErrorType::InvalidRequest, - .. - } - )); - */ - - // ==> executing the action without enough right - let no_right_key = KeyBuilder::new(&description); - let no_right_key = client.create_key(no_right_key).await.unwrap(); - - // backup the master key for cleanup at the end of the test - let master_client = client.clone(); - client.api_key = Arc::new(no_right_key.key.clone()); - - let error = client.update_key(key).await.unwrap_err(); - - assert!(matches!( - error, - Error::Meilisearch(MeilisearchError { - error_code: ErrorCode::InvalidApiKey, - error_type: ErrorType::Auth, - .. - }) - )); - - // cleanup - master_client.delete_key(&*client.api_key).await.unwrap(); - } - #[meilisearch_test] async fn test_get_index(client: Client, index_uid: String) -> Result<(), Error> { let task = client.create_index(&index_uid, None).await?; diff --git a/src/key.rs b/src/key.rs index 56376bb5..ab0d6712 100644 --- a/src/key.rs +++ b/src/key.rs @@ -6,20 +6,23 @@ use crate::{client::Client, errors::Error}; /// Represent a [meilisearch key](https://docs.meilisearch.com/reference/api/keys.html#returned-fields) /// You can get a [Key] from the [Client::get_key] method. /// Or you can create a [Key] with the [KeyBuilder::create] or [Client::create_key] methods. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Key { #[serde(skip_serializing_if = "Vec::is_empty")] pub actions: Vec, #[serde(skip_serializing, with = "time::serde::rfc3339")] pub created_at: OffsetDateTime, - pub description: String, + pub description: Option, + pub name: Option, #[serde(with = "time::serde::rfc3339::option")] pub expires_at: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub indexes: Vec, #[serde(skip_serializing)] pub key: String, + #[serde(skip_serializing)] + pub uid: String, #[serde(skip_serializing, with = "time::serde::rfc3339")] pub updated_at: OffsetDateTime, } @@ -37,23 +40,23 @@ impl Key { /// # /// # futures::executor::block_on(async move { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// - /// let mut key = KeyBuilder::new("My little lovely test key") + /// let description = "My not so little lovely test key".to_string(); + /// let mut key = KeyBuilder::new() /// .with_action(Action::DocumentsAdd) /// .with_index("*") - /// .create(&client).await.unwrap(); + /// .with_description(&description) + /// .execute(&client).await.unwrap(); /// - /// key.with_description("My not so little lovely test key"); - /// # assert_eq!(key.description, "My not so little lovely test key".to_string()); + /// # assert_eq!(key.description, Some(description)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` pub fn with_description(&mut self, desc: impl AsRef) -> &mut Self { - self.description = desc.as_ref().to_string(); + self.description = Some(desc.as_ref().to_string()); self } - /// Add a set of actions the [Key] will be able to execute. + /// Update the name of the key. /// /// # Example /// @@ -65,179 +68,323 @@ impl Key { /// # /// # futures::executor::block_on(async move { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// - /// let mut key = KeyBuilder::new("My little lovely test key") + /// let name = "lovely key".to_string(); + /// let mut key = KeyBuilder::new() /// .with_action(Action::DocumentsAdd) /// .with_index("*") - /// .create(&client).await.unwrap(); + /// .execute(&client).await.unwrap(); /// - /// key.with_actions([Action::DocumentsGet, Action::DocumentsDelete]); + /// key.with_name(&name); + /// # assert_eq!(key.name, Some(name)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` - pub fn with_actions(&mut self, actions: impl IntoIterator) -> &mut Self { - self.actions.extend(actions); + pub fn with_name(&mut self, desc: impl AsRef) -> &mut Self { + self.name = Some(desc.as_ref().to_string()); self } - /// Add one action the [Key] will be able to execute. + /// Update the [Key]. /// /// # Example /// /// ``` - /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; + /// # use meilisearch_sdk::{key::KeyBuilder, client::Client}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # futures::executor::block_on(async move { - /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// - /// let mut key = KeyBuilder::new("My little lovely test key") - /// .with_action(Action::DocumentsAdd) - /// .with_index("*") - /// .create(&client).await.unwrap(); + /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut key = KeyBuilder::new() + /// .execute(&client).await.unwrap(); + /// let description = "My not so little lovely test key".to_string(); + /// key.with_description(&description); + /// let key = key.update(&client).await.unwrap(); /// - /// key.with_action(Action::DocumentsGet); + /// # assert_eq!(key.description, Some(description)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` - pub fn with_action(&mut self, action: Action) -> &mut Self { - self.actions.push(action); - self + pub async fn update(&self, client: &Client) -> Result { + // only send description and name + let mut key_update = KeyUpdater::new(self); + + if let Some(ref description) = self.description { + key_update.with_description(description); + } + if let Some(ref name) = self.name { + key_update.with_name(name); + } + + key_update.execute(client).await } - /// Update the expiration date of the [Key]. + /// Delete the [Key]. /// /// # Example /// /// ``` - /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; - /// use time::{OffsetDateTime, Duration}; + /// # use meilisearch_sdk::{key::KeyBuilder, client::Client}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # futures::executor::block_on(async move { - /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// - /// let mut key = KeyBuilder::new("My little lovely test key") - /// .with_action(Action::DocumentsAdd) - /// .with_index("*") - /// .create(&client).await.unwrap(); + /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut key = KeyBuilder::new() + /// .execute(&client).await.unwrap(); /// - /// // update the epiry date of the key to two weeks from now - /// key.with_expires_at(OffsetDateTime::now_utc() + Duration::WEEK * 2); - /// # client.delete_key(key).await.unwrap(); + /// client.delete_key(key).await.unwrap(); /// # }); /// ``` - pub fn with_expires_at(&mut self, expires_at: OffsetDateTime) -> &mut Self { - self.expires_at = Some(expires_at); + pub async fn delete(&self, client: &Client) -> Result<(), Error> { + client.delete_key(self).await + } +} + +impl AsRef for Key { + fn as_ref(&self) -> &str { + &self.key + } +} + +impl AsRef for Key { + fn as_ref(&self) -> &Key { self } +} - /// Update the indexes the [Key] can manage. +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct KeyUpdater { + pub description: Option, + pub name: Option, + #[serde(skip_serializing)] + pub key: String, +} + +impl KeyUpdater { + pub fn new(key_or_uid: impl AsRef) -> KeyUpdater { + KeyUpdater { + description: None, + name: None, + key: key_or_uid.as_ref().to_string(), + } + } + + /// Update the description of the key. /// /// # Example /// /// ``` - /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; + /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client, key::KeyUpdater}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # futures::executor::block_on(async move { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// - /// let mut key = KeyBuilder::new("My little lovely test key") - /// .with_action(Action::DocumentsAdd) - /// .with_index("*") - /// .create(&client).await.unwrap(); - /// - /// key.with_indexes(vec!["test", "movies"]); - /// # client.delete_key(key).await.unwrap(); + /// let mut new_key = KeyBuilder::new() + /// .execute(&client) + /// .await + /// .unwrap(); + /// + /// let description = "My not so little lovely test key".to_string(); + /// let mut key_update = KeyUpdater::new(new_key) + /// .with_description(&description) + /// .execute(&client) + /// .await + /// .unwrap(); + /// + /// # assert_eq!(key_update.description, Some(description)); + /// # client.delete_key(key_update).await.unwrap(); /// # }); /// ``` - pub fn with_indexes( - &mut self, - indexes: impl IntoIterator>, - ) -> &mut Self { - self.indexes = indexes - .into_iter() - .map(|index| index.as_ref().to_string()) - .collect(); + pub fn with_description(&mut self, desc: impl AsRef) -> &mut Self { + self.description = Some(desc.as_ref().to_string()); self } - /// Add one index the [Key] can manage. + /// Update the name of the key. /// /// # Example /// /// ``` - /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; + /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client, key::KeyUpdater}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # futures::executor::block_on(async move { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut new_key = KeyBuilder::new() + /// .execute(&client) + /// .await + /// .unwrap(); /// - /// let mut key = KeyBuilder::new("My little lovely test key") - /// .with_action(Action::DocumentsAdd) - /// .with_index("*") - /// .create(&client).await.unwrap(); + /// let name = "lovely key".to_string(); /// - /// key.with_index("test"); - /// # client.delete_key(key).await.unwrap(); + /// let mut key_update = KeyUpdater::new(new_key) + /// .with_name(&name) + /// .execute(&client) + /// .await + /// .unwrap(); + /// + /// # assert_eq!(key_update.name, Some(name)); + /// # client.delete_key(key_update).await.unwrap(); /// # }); /// ``` - pub fn with_index(&mut self, index: impl AsRef) -> &mut Self { - self.indexes.push(index.as_ref().to_string()); + pub fn with_name(&mut self, desc: impl AsRef) -> &mut Self { + self.name = Some(desc.as_ref().to_string()); self } - /// Update the [Key]. + /// Update a [Key] using the [KeyUpdater]. /// /// # Example /// /// ``` - /// # use meilisearch_sdk::{key::KeyBuilder, client::Client}; + /// # use meilisearch_sdk::{key::KeyBuilder, key::KeyUpdater, client::Client}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let mut key = KeyBuilder::new("My little lovely test key") - /// .create(&client).await.unwrap(); - /// - /// # assert_eq!(key.description, "My little lovely test key"); - /// - /// key.with_description("My not so little lovely test key"); - /// let key = key.update(&client).await.unwrap(); + /// let description = "My little lovely test key".to_string(); + /// let key = KeyBuilder::new() + /// .execute(&client).await.unwrap(); /// - /// # assert_eq!(key.description, "My not so little lovely test key".to_string()); + /// let mut key_update = KeyUpdater::new(&key.key); + /// key_update.with_description(&description).execute(&client).await; /// + /// assert_eq!(key_update.description, Some(description)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` - pub async fn update(&self, client: &Client) -> Result { + pub async fn execute(&self, client: &Client) -> Result { client.update_key(self).await } } -impl AsRef for Key { +impl AsRef for KeyUpdater { fn as_ref(&self) -> &str { &self.key } } -impl AsRef for Key { - fn as_ref(&self) -> &Key { +impl AsRef for KeyUpdater { + fn as_ref(&self) -> &KeyUpdater { self } } +#[derive(Debug, Serialize, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct KeysQuery { + /// The number of documents to skip. + /// If the value of the parameter `offset` is `n`, the `n` first documents (ordered by relevance) will not be returned. + /// This is helpful for pagination. + /// + /// Example: If you want to skip the first document, set offset to `1`. + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option, + /// The maximum number of documents returned. + /// If the value of the parameter `limit` is `n`, there will never be more than `n` documents in the response. + /// This is helpful for pagination. + /// + /// Example: If you don't want to get more than two documents, set limit to `2`. + /// Default: `20` + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +impl KeysQuery { + /// Create a [KeysQuery] with only a description. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeysQuery}; + /// let builder = KeysQuery::new(); + /// ``` + pub fn new() -> KeysQuery { + Self::default() + } + + /// Specify the offset. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeysQuery, key::Action, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut keys = KeysQuery::new() + /// .with_offset(1) + /// .execute(&client).await.unwrap(); + /// + /// # assert_eq!(keys.results.len(), 1); + /// # }); + /// ``` + pub fn with_offset(&mut self, offset: usize) -> &mut KeysQuery { + self.offset = Some(offset); + self + } + + /// Specify the maximum number of keys to return. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeysQuery, key::Action, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut keys = KeysQuery::new() + /// .with_limit(1) + /// .execute(&client).await.unwrap(); + /// + /// # assert_eq!(keys.results.len(), 1); + /// # }); + /// ``` + pub fn with_limit(&mut self, limit: usize) -> &mut KeysQuery { + self.limit = Some(limit); + self + } + + /// Get [Key]'s. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeysQuery, key::Action, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut keys = KeysQuery::new() + /// .with_limit(1) + /// .execute(&client).await.unwrap(); + /// + /// # assert_eq!(keys.results.len(), 1); + /// # }); + /// ``` + pub async fn execute(&self, client: &Client) -> Result { + client.get_keys_with(self).await + } +} + /// The [KeyBuilder] is an analog to the [Key] type but without all the fields managed by Meilisearch. /// It's used to create [Key]. /// @@ -251,42 +398,41 @@ impl AsRef for Key { /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); +/// let description = "My little lovely test key".to_string(); +/// let key = KeyBuilder::new() +/// .with_description(&description) +/// .execute(&client).await.unwrap(); /// -/// let key = KeyBuilder::new("My little lovely test key") -/// .with_action(Action::DocumentsAdd) -/// .with_index("*") -/// .create(&client).await.unwrap(); -/// -/// assert_eq!(key.description, "My little lovely test key"); +/// # assert_eq!(key.description, Some(description)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] pub struct KeyBuilder { pub actions: Vec, - pub description: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub uid: Option, #[serde(with = "time::serde::rfc3339::option")] pub expires_at: Option, pub indexes: Vec, } impl KeyBuilder { - /// Create a [KeyBuilder] with only a description. + /// Create a [KeyBuilder]. /// /// # Example /// /// ``` /// # use meilisearch_sdk::{key::KeyBuilder}; - /// let builder = KeyBuilder::new("My little lovely test key"); - /// ``` - pub fn new(description: impl AsRef) -> KeyBuilder { - Self { - actions: Vec::new(), - description: description.as_ref().to_string(), - expires_at: None, - indexes: Vec::new(), - } + /// let builder = KeyBuilder::new(); + /// ``` + pub fn new() -> KeyBuilder { + Self::default() } /// Declare a set of actions the [Key] will be able to execute. @@ -295,7 +441,7 @@ impl KeyBuilder { /// /// ``` /// # use meilisearch_sdk::key::{KeyBuilder, Action}; - /// let mut builder = KeyBuilder::new("My little lovely test key"); + /// let mut builder = KeyBuilder::new(); /// builder.with_actions(vec![Action::Search, Action::DocumentsAdd]); /// ``` pub fn with_actions(&mut self, actions: impl IntoIterator) -> &mut Self { @@ -309,7 +455,7 @@ impl KeyBuilder { /// /// ``` /// # use meilisearch_sdk::key::{KeyBuilder, Action}; - /// let mut builder = KeyBuilder::new("My little lovely test key"); + /// let mut builder = KeyBuilder::new(); /// builder.with_action(Action::DocumentsAdd); /// ``` pub fn with_action(&mut self, action: Action) -> &mut Self { @@ -324,7 +470,7 @@ impl KeyBuilder { /// ``` /// # use meilisearch_sdk::{key::KeyBuilder}; /// use time::{OffsetDateTime, Duration}; - /// let mut builder = KeyBuilder::new("My little lovely test key"); + /// let mut builder = KeyBuilder::new(); /// // create a key that expires in two weeks from now /// builder.with_expires_at(OffsetDateTime::now_utc() + Duration::WEEK * 2); /// ``` @@ -338,9 +484,22 @@ impl KeyBuilder { /// # Example /// /// ``` - /// # use meilisearch_sdk::{key::KeyBuilder}; - /// let mut builder = KeyBuilder::new("My little lovely test key"); - /// builder.with_indexes(vec!["test", "movies"]); + /// # use meilisearch_sdk::{key::KeyBuilder, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut key = KeyBuilder::new() + /// .with_indexes(vec!["test", "movies"]) + /// .execute(&client) + /// .await + /// .unwrap(); + /// + /// assert_eq!(vec!["test", "movies"], key.indexes); + /// # client.delete_key(key).await.unwrap(); + /// # }); /// ``` pub fn with_indexes( &mut self, @@ -359,7 +518,7 @@ impl KeyBuilder { /// /// ``` /// # use meilisearch_sdk::{key::KeyBuilder}; - /// let mut builder = KeyBuilder::new("My little lovely test key"); + /// let mut builder = KeyBuilder::new(); /// builder.with_index("test"); /// ``` pub fn with_index(&mut self, index: impl AsRef) -> &mut Self { @@ -367,6 +526,88 @@ impl KeyBuilder { self } + /// Add a description to the key. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let description = "My not so little lovely test key".to_string(); + /// + /// let mut key = KeyBuilder::new() + /// .with_description(&description) + /// .execute(&client).await.unwrap(); + /// + /// # assert_eq!(key.description, Some(description)); + /// # client.delete_key(key).await.unwrap(); + /// # }); + /// ``` + pub fn with_description(&mut self, desc: impl AsRef) -> &mut Self { + self.description = Some(desc.as_ref().to_string()); + self + } + + /// Add a name to the key. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let name = "lovely key".to_string(); + /// + /// let mut key = KeyBuilder::new() + /// .with_name(&name) + /// .execute(&client).await.unwrap(); + /// + /// # assert_eq!(key.name, Some(name)); + /// # client.delete_key(key).await.unwrap(); + /// # }); + /// ``` + pub fn with_name(&mut self, desc: impl AsRef) -> &mut Self { + self.name = Some(desc.as_ref().to_string()); + self + } + + /// Add an uid to the key. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{key::KeyBuilder, key::Action, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let uid = "93bcd7fb-2196-4fd9-acb7-3fca8a96e78f".to_string(); + /// + /// let mut key = KeyBuilder::new() + /// .with_uid(&uid) + /// .execute(&client).await.unwrap(); + /// + /// + /// # assert_eq!(key.uid, uid); + /// # client.delete_key(key).await.unwrap(); + /// # }); + /// ``` + pub fn with_uid(&mut self, desc: impl AsRef) -> &mut Self { + self.uid = Some(desc.as_ref().to_string()); + self + } + /// Create a [Key] from the builder. /// /// # Example @@ -379,14 +620,16 @@ impl KeyBuilder { /// # /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let key = KeyBuilder::new("My little lovely test key") - /// .create(&client).await.unwrap(); + /// let description = "My little lovely test key".to_string(); + /// let key = KeyBuilder::new() + /// .with_description(&description) + /// .execute(&client).await.unwrap(); /// - /// assert_eq!(key.description, "My little lovely test key"); + /// # assert_eq!(key.description, Some(description)); /// # client.delete_key(key).await.unwrap(); /// # }); /// ``` - pub async fn create(&self, client: &Client) -> Result { + pub async fn execute(&self, client: &Client) -> Result { client.create_key(self).await } } @@ -447,4 +690,23 @@ pub enum Action { /// Provides access to the [get Meilisearch version](https://docs.meilisearch.com/reference/api/version.md#get-version-of-meilisearch) endpoint. #[serde(rename = "version")] Version, + /// Provides access to the [get Key](https://docs.meilisearch.com/reference/api/keys.html#get-one-key) and [get Keys](https://docs.meilisearch.com/reference/api/keys.html#get-all-keys) endpoints. + #[serde(rename = "keys.get")] + KeyGet, + /// Provides access to the [create key](https://docs.meilisearch.com/reference/api/keys.html#create-a-key) endpoint. + #[serde(rename = "keys.create")] + KeyCreate, + /// Provides access to the [update key](https://docs.meilisearch.com/reference/api/keys.html#update-a-key) endpoint. + #[serde(rename = "keys.update")] + KeyUpdate, + /// Provides access to the [delete key](https://docs.meilisearch.com/reference/api/keys.html#delete-a-key) endpoint. + #[serde(rename = "keys.delete")] + KeyDelete, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct KeysResults { + pub results: Vec, + pub limit: u32, + pub offset: u32, } diff --git a/src/search.rs b/src/search.rs index 954bedaf..db93f4dd 100644 --- a/src/search.rs +++ b/src/search.rs @@ -734,10 +734,10 @@ mod tests { setup_test_index(&client, &index).await?; let meilisearch_host = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); - let key = KeyBuilder::new("key for generate_tenant_token test") + let key = KeyBuilder::new() .with_action(Action::All) .with_index("*") - .create(&client) + .execute(&client) .await .unwrap(); let allowed_client = Client::new(meilisearch_host, key.key); From 5ce98c7910b45d13d06f556c4172b3920d464856 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:48:54 +0200 Subject: [PATCH 06/19] Update indexes for v0.28.0 (#315) * Update task api v0.28.0 * Update tests to be relevant with the structures where they are ran * Update types in code-samples * Remove clearAll task type * Add index_uid as an optional in the Tasks * Add comments on best builder implementation * Add execute function on get_tasks * Update code samples * Remove out of context comment * Fix tests * Fix pagination tests * Update HTTP methods for v0.28.0 * Fix clippy * Remove comment of task tests since the tests are now sucesful * Fix doc tests * Update keys for v0.28.0 * Fix get tasks inside index structure * Make description and name optional in keys * Fix none doc tests with new get_tasks api * Add from and limit in tasks params * Add warning on failing test * Update keys design * Update task API * Remove useless comment * Remove client as mandatory parameter for the keyUpdater * Add doc and tests on doc * Fix docs tests on keys in client * Fix docs tests * Remove dbg * Add with_uid filter * Add new actions on key creation * Remove new line at the start of docs * Fix clippy errors * Rename type in Task structure * Removed useless newlines * Fix typo in comment * Add missing semi-column * Update indexes api for v0.28.0 * Improve doc comments * Change indexes methods * Add index query and index updater * Add documentation and doc tests on indexes routes * Add tests on get indexes with params * Add test on index update * Fix clippy * Improve limit to avoid flacky test * Update src/client.rs Co-authored-by: Tamo * Update src/client.rs Co-authored-by: Tamo * Remove useless newlines * Rollback changes on keys api * Fix duplicated closing bracket * Fix failing tests * Remove useless comment Co-authored-by: Tamo --- .code-samples.meilisearch.yaml | 2 +- src/client.rs | 167 ++++++++++--- src/indexes.rs | 439 ++++++++++++++++++++++++++++++--- 3 files changed, 550 insertions(+), 58 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index eb7673ab..196cc9a5 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -6,7 +6,7 @@ get_one_index_1: |- let movies: Index = client.get_index("movies").await.unwrap(); list_all_indexes_1: |- - let indexes: Vec = client.list_all_indexes().await.unwrap(); + let indexes: IndexesResults = client.list_all_indexes().await.unwrap(); create_an_index_1: |- client.create_index("movies", Some("id")).await.unwrap(); update_an_index_1: |- diff --git a/src/client.rs b/src/client.rs index bd1e2812..bf5c60dd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -41,7 +41,23 @@ impl Client { } } - /// List all [Index]es and returns values as instances of [Index]. + fn parse_indexes_results_from_value(&self, value: Value) -> Result { + let raw_indexes = value["results"].as_array().unwrap(); + + let indexes_results = IndexesResults { + limit: value["limit"].as_u64().unwrap() as u32, + offset: value["offset"].as_u64().unwrap() as u32, + total: value["total"].as_u64().unwrap() as u32, + results: raw_indexes + .iter() + .map(|raw_index| Index::from_value(raw_index.clone(), self.clone())) + .collect::>()?, + }; + + Ok(indexes_results) + } + + /// List all [Index]es with query parameters and returns values as instances of [Index]. /// /// # Example /// @@ -55,16 +71,44 @@ impl Client { /// // create the client /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// - /// let indexes: Vec = client.list_all_indexes().await.unwrap(); + /// let indexes: IndexesResults = client.list_all_indexes().await.unwrap(); /// println!("{:?}", indexes); /// # }); /// ``` - pub async fn list_all_indexes(&self) -> Result, Error> { - self.list_all_indexes_raw() - .await? - .into_iter() - .map(|index| Index::from_value(index, self.clone())) - .collect() + pub async fn list_all_indexes(&self) -> Result { + let value = self.list_all_indexes_raw().await?; + let indexes_results = self.parse_indexes_results_from_value(value)?; + Ok(indexes_results) + } + + /// List all [Index]es and returns values as instances of [Index]. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// // create the client + /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let mut query = IndexesQuery::new(&client); + /// query.with_limit(1); + /// let indexes: IndexesResults = client.list_all_indexes_with(&query).await.unwrap(); + /// + /// assert_eq!(indexes.limit, 1); + /// # }); + /// ``` + pub async fn list_all_indexes_with( + &self, + indexes_query: &IndexesQuery<'_>, + ) -> Result { + let value = self.list_all_indexes_raw_with(indexes_query).await?; + let indexes_results = self.parse_indexes_results_from_value(value)?; + + Ok(indexes_results) } /// List all [Index]es and returns as Json. @@ -85,8 +129,8 @@ impl Client { /// println!("{:?}", json_indexes); /// # }); /// ``` - pub async fn list_all_indexes_raw(&self) -> Result, Error> { - let json_indexes = request::<(), Vec>( + pub async fn list_all_indexes_raw(&self) -> Result { + let json_indexes = request::<(), Value>( &format!("{}/indexes", self.host), &self.api_key, Method::Get(()), @@ -97,6 +141,42 @@ impl Client { Ok(json_indexes) } + /// List all [Index]es with query parameters and returns as Json. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// // create the client + /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// + /// let mut query = IndexesQuery::new(&client); + /// query.with_limit(1); + /// let json_indexes = client.list_all_indexes_raw_with(&query).await.unwrap(); + /// + /// println!("{:?}", json_indexes); + /// # }); + /// ``` + pub async fn list_all_indexes_raw_with( + &self, + indexes_query: &IndexesQuery<'_>, + ) -> Result { + let json_indexes = request::<&IndexesQuery, Value>( + &format!("{}/indexes", self.host), + &self.api_key, + Method::Get(indexes_query), + 200, + ) + .await?; + + Ok(json_indexes) + } + /// Get an [Index], this index should already exist. /// /// # Example @@ -158,13 +238,7 @@ impl Client { /// Create a corresponding object of an [Index] without any check or doing an HTTP call. pub fn index(&self, uid: impl Into) -> Index { - Index { - uid: Arc::new(uid.into()), - client: self.clone(), - primary_key: None, - created_at: None, - updated_at: None, - } + Index::new(uid, self.clone()) } /// Create an [Index]. @@ -225,15 +299,31 @@ impl Client { } /// Alias for [Client::list_all_indexes]. - pub async fn get_indexes(&self) -> Result, Error> { + pub async fn get_indexes(&self) -> Result { self.list_all_indexes().await } + /// Alias for [Client::list_all_indexes_with]. + pub async fn get_indexes_with( + &self, + indexes_query: &IndexesQuery<'_>, + ) -> Result { + self.list_all_indexes_with(indexes_query).await + } + /// Alias for [Client::list_all_indexes_raw]. - pub async fn get_indexes_raw(&self) -> Result, Error> { + pub async fn get_indexes_raw(&self) -> Result { self.list_all_indexes_raw().await } + /// Alias for [Client::list_all_indexes_raw_with]. + pub async fn get_indexes_raw_with( + &self, + indexes_query: &IndexesQuery<'_>, + ) -> Result { + self.list_all_indexes_raw_with(indexes_query).await + } + /// Get stats of all indexes. /// /// # Example @@ -873,6 +963,7 @@ mod tests { } #[meilisearch_test] + async fn test_error_delete_key(mut client: Client, name: String) { // ==> accessing a key that does not exist let error = client.delete_key("invalid_key").await.unwrap_err(); @@ -887,9 +978,9 @@ mod tests { // ==> executing the action without enough right let mut key = KeyBuilder::new(); + key.with_name(&name); let key = client.create_key(key).await.unwrap(); - let master_key = client.api_key.clone(); // this key has no right client.api_key = Arc::new(key.key.clone()); @@ -1058,19 +1149,39 @@ mod tests { } #[meilisearch_test] - async fn test_list_all_indexes(client: Client, index: Index) { + async fn test_list_all_indexes(client: Client) { let all_indexes = client.list_all_indexes().await.unwrap(); - assert!(all_indexes.len() > 0); - assert!(all_indexes.iter().any(|idx| idx.uid == index.uid)); + + assert_eq!(all_indexes.limit, 20); + assert_eq!(all_indexes.offset, 0); } #[meilisearch_test] - async fn test_list_all_indexes_raw(client: Client, index: Index) { + async fn test_list_all_indexes_with_params(client: Client) { + let mut query = IndexesQuery::new(&client); + query.with_limit(1); + let all_indexes = client.list_all_indexes_with(&query).await.unwrap(); + + assert_eq!(all_indexes.limit, 1); + assert_eq!(all_indexes.offset, 0); + } + + #[meilisearch_test] + async fn test_list_all_indexes_raw(client: Client) { let all_indexes_raw = client.list_all_indexes_raw().await.unwrap(); - assert!(all_indexes_raw.len() > 0); - assert!(all_indexes_raw - .iter() - .any(|idx| idx["uid"] == json!(index.uid.to_string()))); + + assert_eq!(all_indexes_raw["limit"], json!(20)); + assert_eq!(all_indexes_raw["offset"], json!(0)); + } + + #[meilisearch_test] + async fn test_list_all_indexes_raw_with_params(client: Client) { + let mut query = IndexesQuery::new(&client); + query.with_limit(1); + let all_indexes_raw = client.list_all_indexes_raw_with(&query).await.unwrap(); + + assert_eq!(all_indexes_raw["limit"], json!(1)); + assert_eq!(all_indexes_raw["offset"], json!(0)); } #[meilisearch_test] diff --git a/src/indexes.rs b/src/indexes.rs index 067a9ab1..959686fb 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -1,7 +1,6 @@ use crate::{client::Client, errors::Error, request::*, search::*, task_info::TaskInfo, tasks::*}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::json; -use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration}; +use std::{collections::HashMap, fmt::Display, time::Duration}; use time::OffsetDateTime; /// An index containing [Document]s. @@ -37,7 +36,7 @@ use time::OffsetDateTime; /// # }); /// ``` /// -/// Or, if you know the index already exist remotely you can create an `Index` with the [Client::index] function. +/// Or, if you know the index already exist remotely you can create an [Index] with its builder. /// ``` /// # use meilisearch_sdk::{client::*, indexes::*}; /// # @@ -47,27 +46,39 @@ use time::OffsetDateTime; /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// -/// // use the implicit index creation if the index already exist or /// // Meilisearch would be able to create the index if it does not exist during: /// // - the documents addition (add and update routes) /// // - the settings update -/// let movies = client.index("index"); +/// let movies = Index::new("movies", client); /// -/// // do something with the index +/// assert_eq!(movies.uid, "movies"); /// # }); /// ``` -#[derive(Debug, Clone)] +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] pub struct Index { - pub(crate) uid: Arc, - pub(crate) client: Client, - pub(crate) primary_key: Option, - pub created_at: Option, + #[serde(skip_serializing)] + pub client: Client, + pub uid: String, + #[serde(with = "time::serde::rfc3339::option")] pub updated_at: Option, + #[serde(with = "time::serde::rfc3339::option")] + pub created_at: Option, + pub primary_key: Option, } impl Index { + pub fn new(uid: impl Into, client: Client) -> Index { + Index { + uid: uid.into(), + client, + primary_key: None, + created_at: None, + updated_at: None, + } + } /// Internal Function to create an [Index] from `serde_json::Value` and [Client] - pub(crate) fn from_value(v: serde_json::Value, client: Client) -> Result { + pub(crate) fn from_value(raw_index: serde_json::Value, client: Client) -> Result { #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct IndexFromSerde { @@ -79,10 +90,10 @@ impl Index { primaryKey: Option, } - let i: IndexFromSerde = serde_json::from_value(v).map_err(Error::ParseError)?; + let i: IndexFromSerde = serde_json::from_value(raw_index).map_err(Error::ParseError)?; Ok(Index { - uid: Arc::new(i.uid), + uid: i.uid, client, created_at: i.createdAt, updated_at: i.updatedAt, @@ -90,18 +101,50 @@ impl Index { }) } - /// Set the primary key of the index. + /// Update an [Index]. /// - /// If you prefer, you can use the method [Index::set_primary_key], which is an alias. - pub async fn update(&self, primary_key: impl AsRef) -> Result<(), Error> { - request::( - &format!("{}/indexes/{}", self.client.host, self.uid), - &self.client.api_key, - Method::Patch(json!({ "primaryKey": primary_key.as_ref() })), - 200, - ) - .await?; - Ok(()) + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let mut index = client + /// # .create_index("index_update", None) + /// # .await + /// # .unwrap() + /// # .wait_for_completion(&client, None, None) + /// # .await + /// # .unwrap() + /// # // Once the task finished, we try to create an `Index` out of it + /// # .try_make_index(&client) + /// # .unwrap(); + /// + /// index.primary_key = Some("special_id".to_string()); + /// let task = index.update() + /// .await + /// .unwrap() + /// .wait_for_completion(&client, None, None) + /// .await + /// .unwrap(); + /// + /// let index = client.get_index("index_update").await.unwrap(); + /// assert_eq!(index.primary_key, Some("special_id".to_string())); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn update(&self) -> Result { + let mut index_update = IndexUpdater::new(self, &self.client); + + if let Some(ref primary_key) = self.primary_key { + index_update.with_primary_key(primary_key); + } + + index_update.execute().await } /// Delete the index. @@ -631,8 +674,13 @@ impl Index { } /// Alias for the [Index::update] method. - pub async fn set_primary_key(&self, primary_key: impl AsRef) -> Result<(), Error> { - self.update(primary_key).await + pub async fn set_primary_key( + &mut self, + primary_key: impl AsRef, + ) -> Result { + self.primary_key = Some(primary_key.as_ref().to_string()); + + self.update().await } /// Fetch the information of the index as a raw JSON [Index], this index should already exist. @@ -659,7 +707,7 @@ impl Index { /// ``` /// If you use it directly from the [Client], you can use the method [Client::get_raw_index], which is the equivalent method from the client. pub async fn fetch_info(&mut self) -> Result<(), Error> { - let v = self.client.get_raw_index(self.uid.as_ref()).await?; + let v = self.client.get_raw_index(&self.uid).await?; *self = Index::from_value(v, self.client.clone())?; Ok(()) } @@ -1045,6 +1093,163 @@ impl AsRef for Index { } } +/// An [IndexUpdater] used to update the specifics of an index +/// +/// # Example +/// +/// ``` +/// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}}; +/// # +/// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); +/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); +/// # +/// # futures::executor::block_on(async move { +/// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); +/// # let index = client +/// # .create_index("index_updater", None) +/// # .await +/// # .unwrap() +/// # .wait_for_completion(&client, None, None) +/// # .await +/// # .unwrap() +/// # // Once the task finished, we try to create an `Index` out of it +/// # .try_make_index(&client) +/// # .unwrap(); +/// +/// let task = IndexUpdater::new("index_updater", &client) +/// .with_primary_key("special_id") +/// .execute() +/// .await +/// .unwrap() +/// .wait_for_completion(&client, None, None) +/// .await +/// .unwrap(); +/// +/// let index = client.get_index("index_updater").await.unwrap(); +/// assert_eq!(index.primary_key, Some("special_id".to_string())); +/// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); +/// # }); +/// ``` +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct IndexUpdater<'a> { + #[serde(skip)] + pub client: &'a Client, + #[serde(skip_serializing)] + pub uid: String, + pub primary_key: Option, +} + +impl<'a> IndexUpdater<'a> { + pub fn new(uid: impl AsRef, client: &Client) -> IndexUpdater { + IndexUpdater { + client, + primary_key: None, + uid: uid.as_ref().to_string(), + } + } + /// Define the new primary_key to set on the [Index] + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let index = client + /// # .create_index("index_updater_with_primary_key", None) + /// # .await + /// # .unwrap() + /// # .wait_for_completion(&client, None, None) + /// # .await + /// # .unwrap() + /// # // Once the task finished, we try to create an `Index` out of it + /// # .try_make_index(&client) + /// # .unwrap(); + /// + /// let task = IndexUpdater::new("index_updater_with_primary_key", &client) + /// .with_primary_key("special_id") + /// .execute() + /// .await + /// .unwrap() + /// .wait_for_completion(&client, None, None) + /// .await + /// .unwrap(); + /// + /// let index = client.get_index("index_updater_with_primary_key").await.unwrap(); + /// assert_eq!(index.primary_key, Some("special_id".to_string())); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub fn with_primary_key(&mut self, primary_key: impl AsRef) -> &mut Self { + self.primary_key = Some(primary_key.as_ref().to_string()); + self + } + + /// Execute the update of an [Index] using the [IndexUpdater] + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, task_info::*, tasks::{Task, SucceededTask}}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let index = client + /// # .create_index("index_updater_execute", None) + /// # .await + /// # .unwrap() + /// # .wait_for_completion(&client, None, None) + /// # .await + /// # .unwrap() + /// # // Once the task finished, we try to create an `Index` out of it + /// # .try_make_index(&client) + /// # .unwrap(); + /// + /// let task = IndexUpdater::new("index_updater_execute", &client) + /// .with_primary_key("special_id") + /// .execute() + /// .await + /// .unwrap() + /// .wait_for_completion(&client, None, None) + /// .await + /// .unwrap(); + /// + /// let index = client.get_index("index_updater_execute").await.unwrap(); + /// assert_eq!(index.primary_key, Some("special_id".to_string())); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn execute(&'a self) -> Result { + request::<&IndexUpdater, TaskInfo>( + &format!("{}/indexes/{}", self.client.host, self.uid), + &self.client.api_key, + Method::Patch(self), + 202, + ) + .await + } +} + +impl AsRef for IndexUpdater<'_> { + fn as_ref(&self) -> &str { + &self.uid + } +} + +impl<'a> AsRef> for IndexUpdater<'a> { + fn as_ref(&self) -> &IndexUpdater<'a> { + self + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct IndexStats { @@ -1053,11 +1258,187 @@ pub struct IndexStats { pub field_distribution: HashMap, } +// An [IndexesQuery] containing filter and pagination parameters when searching for [Index]es +/// +/// # Example +/// +/// ``` +/// # use meilisearch_sdk::{client::*, indexes::*}; +/// # +/// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); +/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); +/// # +/// # futures::executor::block_on(async move { +/// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); +/// # let index = client +/// # .create_index("index_query_builder", None) +/// # .await +/// # .unwrap() +/// # .wait_for_completion(&client, None, None) +/// # .await +/// # .unwrap() +/// # // Once the task finished, we try to create an `Index` out of it +/// # .try_make_index(&client) +/// # .unwrap(); +/// let mut indexes = IndexesQuery::new(&client) +/// .with_limit(1) +/// .execute().await.unwrap(); +/// +/// # assert_eq!(indexes.results.len(), 1); +/// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); +/// # }); +/// ``` +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct IndexesQuery<'a> { + #[serde(skip_serializing)] + pub client: &'a Client, + /// The number of [Index]es to skip. + /// If the value of the parameter `offset` is `n`, the `n` first indexes will not be returned. + /// This is helpful for pagination. + /// + /// Example: If you want to skip the first index, set offset to `1`. + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option, + + /// The maximum number of [Index]es returned. + /// If the value of the parameter `limit` is `n`, there will never be more than `n` indexes in the response. + /// This is helpful for pagination. + /// + /// Example: If you don't want to get more than two indexes, set limit to `2`. + /// Default: `20` + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +impl<'a> IndexesQuery<'a> { + pub fn new(client: &Client) -> IndexesQuery { + IndexesQuery { + client, + offset: None, + limit: None, + } + } + + /// Specify the offset. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let index = client + /// # .create_index("index_query_with_offset", None) + /// # .await + /// # .unwrap() + /// # .wait_for_completion(&client, None, None) + /// # .await + /// # .unwrap() + /// # // Once the task finished, we try to create an `Index` out of it + /// # .try_make_index(&client) + /// # .unwrap(); + /// + /// let mut indexes = IndexesQuery::new(&client) + /// .with_offset(1) + /// .execute().await.unwrap(); + /// + /// # assert_eq!(indexes.offset, 1); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub fn with_offset(&mut self, offset: usize) -> &mut IndexesQuery<'a> { + self.offset = Some(offset); + self + } + + /// Specify the maximum number of [Index]es to return. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let index = client + /// # .create_index("index_query_with_limit", None) + /// # .await + /// # .unwrap() + /// # .wait_for_completion(&client, None, None) + /// # .await + /// # .unwrap() + /// # // Once the task finished, we try to create an `Index` out of it + /// # .try_make_index(&client) + /// # .unwrap(); + /// let mut indexes = IndexesQuery::new(&client) + /// .with_limit(1) + /// .execute().await.unwrap(); + /// + /// # assert_eq!(indexes.results.len(), 1); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub fn with_limit(&mut self, limit: usize) -> &mut IndexesQuery<'a> { + self.limit = Some(limit); + self + } + /// Get [Index]es. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{indexes::IndexesQuery, client::Client}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # futures::executor::block_on(async move { + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// # let index = client + /// # .create_index("index_query_with_execute", None) + /// # .await + /// # .unwrap() + /// # .wait_for_completion(&client, None, None) + /// # .await + /// # .unwrap() + /// # // Once the task finished, we try to create an `Index` out of it + /// # .try_make_index(&client) + /// # .unwrap(); + /// let mut indexes = IndexesQuery::new(&client) + /// .with_limit(1) + /// .execute().await.unwrap(); + /// + /// # assert_eq!(indexes.results.len(), 1); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn execute(&self) -> Result { + self.client.list_all_indexes_with(self).await + } +} + +#[derive(Debug, Clone)] +pub struct IndexesResults { + pub results: Vec, + pub limit: u32, + pub offset: u32, + pub total: u32, +} + #[cfg(test)] mod tests { use super::*; use meilisearch_test_macro::meilisearch_test; + use serde_json::json; #[meilisearch_test] async fn test_from_value(client: Client) { @@ -1074,7 +1455,7 @@ mod tests { }); let idx = Index { - uid: Arc::new("test_from_value".to_string()), + uid: "test_from_value".to_string(), primary_key: None, created_at: Some(t), updated_at: Some(t), From cf0409b92cc159c301880ddc6717cb5f1172f2e0 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Thu, 25 Aug 2022 14:04:07 +0200 Subject: [PATCH 07/19] Update search for v0.28.0 (#316) * Update task api v0.28.0 * Update tests to be relevant with the structures where they are ran * Update types in code-samples * Remove clearAll task type * Add index_uid as an optional in the Tasks * Add comments on best builder implementation * Add execute function on get_tasks * Update code samples * Remove out of context comment * Fix tests * Fix pagination tests * Update HTTP methods for v0.28.0 * Fix clippy * Remove comment of task tests since the tests are now sucesful * Fix doc tests * Update keys for v0.28.0 * Fix get tasks inside index structure * Make description and name optional in keys * Fix none doc tests with new get_tasks api * Add from and limit in tasks params * Add warning on failing test * Update keys design * Update task API * Remove useless comment * Remove client as mandatory parameter for the keyUpdater * Add doc and tests on doc * Fix docs tests on keys in client * Fix docs tests * Remove dbg * Add with_uid filter * Add new actions on key creation * Remove new line at the start of docs * Fix clippy errors * Rename type in Task structure * Removed useless newlines * Fix typo in comment * Add missing semi-column * Update indexes api for v0.28.0 * Improve doc comments * Change indexes methods * Add index query and index updater * Add documentation and doc tests on indexes routes * Add tests on get indexes with params * Add test on index update * Fix clippy * update nb hits to estimated_nb_hits * remove exhaustive facets count * Change matches to show_matches_position * Change matches_info to matches_position * Update search api for v0.28.1 * Improve comment syntax * Hide client creation in search query builder doc test * Rollback wrong merging * Fix failing key tests --- .code-samples.meilisearch.yaml | 8 +-- README.md | 2 +- src/client.rs | 4 +- src/key.rs | 2 +- src/lib.rs | 2 +- src/search.rs | 96 ++++++++++++++++++++++------------ 6 files changed, 73 insertions(+), 41 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 196cc9a5..ca109c57 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -306,13 +306,13 @@ search_parameter_guide_highlight_tag_1: |- search_parameter_guide_matches_1: |- let results: SearchResults = client.index("movies").search() .with_query("winter feast") - .with_matches(true) + .with_show_matches_position(true) .execute() .await .unwrap(); // Get the matches info - let matched_info: Vec<&HashMap>> = results.hits.iter().map(|r| r.matches_info.as_ref().unwrap()).collect(); + let matched_info: Vec<&HashMap>> = results.hits.iter().map(|r| r.matches_position.as_ref().unwrap()).collect(); settings_guide_synonyms_1: |- let mut synonyms = HashMap::new(); synonyms.insert(String::from("sweater"), vec![String::from("jumper")]); @@ -654,11 +654,11 @@ faceted_search_filter_1: |- faceted_search_facets_distribution_1: |- let results: SearchResults = client.index("movies").search() .with_query("Batman") - .with_facets_distribution(Selectors::Some(&["genres"])) + .with_facets(Selectors::Some(&["genres"])) .execute() .await .unwrap(); - let genres: &HashMap = results.facets_distribution.unwrap().get("genres").unwrap(); + let genres: &HashMap = results.facet_distribution.unwrap().get("genres").unwrap(); faceted_search_walkthrough_filter_1: |- let results: SearchResults = client.index("movies").search() .with_query("thriller") diff --git a/README.md b/README.md index 64821aca..fcf6b2ca 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ Json output: ], "offset": 0, "limit": 20, - "nbHits": 1, + "estimatedTotalHits": 1, "processingTimeMs": 0, "query": "wonder" } diff --git a/src/client.rs b/src/client.rs index bf5c60dd..a7bcc1a9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -450,7 +450,7 @@ impl Client { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// let keys = client.get_keys().await.unwrap(); /// - /// assert_eq!(keys.results.len(), 2); + /// assert_eq!(keys.limit, 20); /// # }); /// ``` pub async fn get_keys(&self) -> Result { @@ -482,7 +482,7 @@ impl Client { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// # let key = client.get_keys().await.unwrap().results.into_iter() /// .find(|k| k.name.as_ref().map_or(false, |name| name.starts_with("Default Search API Key"))); - /// let key_id = key.unwrap().key // enter your API key here, for the example we use the search API key. + /// let key_id = key.unwrap().key; // enter your API key here, for the example we use the search API key. /// let key = client.get_key(key_id).await.unwrap(); /// /// assert_eq!(key.name, Some("Default Search API Key".to_string())); diff --git a/src/key.rs b/src/key.rs index ab0d6712..2af708cd 100644 --- a/src/key.rs +++ b/src/key.rs @@ -329,7 +329,7 @@ impl KeysQuery { /// .with_offset(1) /// .execute(&client).await.unwrap(); /// - /// # assert_eq!(keys.results.len(), 1); + /// # assert_eq!(keys.offset, 1); /// # }); /// ``` pub fn with_offset(&mut self, offset: usize) -> &mut KeysQuery { diff --git a/src/lib.rs b/src/lib.rs index 411ddfbd..21bab82d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,7 +214,7 @@ //! ], //! "offset": 0, //! "limit": 20, -//! "nbHits": 1, +//! "estimatedTotalHits": 1, //! "processingTimeMs": 0, //! "query": "wonder" //! } diff --git a/src/search.rs b/src/search.rs index db93f4dd..c1abaa07 100644 --- a/src/search.rs +++ b/src/search.rs @@ -20,8 +20,8 @@ pub struct SearchResult { #[serde(rename = "_formatted")] pub formatted_result: Option>, /// The object that contains information about the matches. - #[serde(rename = "_matchesInfo")] - pub matches_info: Option>>, + #[serde(rename = "_matchesPosition")] + pub matches_position: Option>>, } #[derive(Deserialize, Debug)] @@ -35,13 +35,9 @@ pub struct SearchResults { /// Number of results returned pub limit: usize, /// Total number of matches - pub nb_hits: usize, - /// Whether nb_hits is exhaustive - pub exhaustive_nb_hits: bool, + pub estimated_total_hits: usize, /// Distribution of the given facets - pub facets_distribution: Option>>, - /// Whether facet_distribution is exhaustive - pub exhaustive_facets_count: Option, + pub facet_distribution: Option>>, /// Processing time of the query pub processing_time_ms: usize, /// Query originating the response @@ -101,19 +97,40 @@ type AttributeToCrop<'a> = (&'a str, Option); /// # Examples /// /// ``` +/// use serde::{Serialize, Deserialize}; /// # use meilisearch_sdk::{client::Client, search::Query, indexes::Index}; /// # /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # +/// #[derive(Serialize, Deserialize, Debug)] +/// struct Movie { +/// name: String, +/// description: String, +/// } +/// +/// # futures::executor::block_on(async move { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); -/// # let index = client.index("does not matter"); -/// let query = Query::new(&index) +/// # let index = client +/// # .create_index("search_query_builder", None) +/// # .await +/// # .unwrap() +/// # .wait_for_completion(&client, None, None) +/// # .await.unwrap() +/// # .try_make_index(&client) +/// # .unwrap(); +/// +/// let mut res = Query::new(&index) /// .with_query("space") /// .with_offset(42) /// .with_limit(21) +/// .execute::() +/// .await +/// .unwrap(); /// -/// let res = query.execute().await?.unwrap(); +/// assert_eq!(res.limit, 21); +/// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); +/// # }); /// ``` /// /// ``` @@ -123,7 +140,7 @@ type AttributeToCrop<'a> = (&'a str, Option); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); -/// # let index = client.index("does not matter"); +/// # let index = client.index("search_query_builder_build"); /// let query = index.search() /// .with_query("space") /// .with_offset(42) @@ -164,7 +181,7 @@ pub struct Query<'a> { /// Default: all attributes found in the documents. #[serde(skip_serializing_if = "Option::is_none")] #[serde(serialize_with = "serialize_with_wildcard")] - pub facets_distribution: Option>, + pub facets: Option>, /// Attributes to sort. #[serde(skip_serializing_if = "Option::is_none")] pub sort: Option<&'a [&'a str]>, @@ -216,7 +233,7 @@ pub struct Query<'a> { /// /// Default: `false` #[serde(skip_serializing_if = "Option::is_none")] - pub matches: Option, + pub show_matches_position: Option, } #[allow(missing_docs)] @@ -229,7 +246,7 @@ impl<'a> Query<'a> { limit: None, filter: None, sort: None, - facets_distribution: None, + facets: None, attributes_to_retrieve: None, attributes_to_crop: None, crop_length: None, @@ -237,13 +254,14 @@ impl<'a> Query<'a> { attributes_to_highlight: None, highlight_pre_tag: None, highlight_post_tag: None, - matches: None, + show_matches_position: None, } } pub fn with_query<'b>(&'b mut self, query: &'a str) -> &'b mut Query<'a> { self.query = Some(query); self } + pub fn with_offset<'b>(&'b mut self, offset: usize) -> &'b mut Query<'a> { self.offset = Some(offset); self @@ -256,11 +274,8 @@ impl<'a> Query<'a> { self.filter = Some(filter); self } - pub fn with_facets_distribution<'b>( - &'b mut self, - facets_distribution: Selectors<&'a [&'a str]>, - ) -> &'b mut Query<'a> { - self.facets_distribution = Some(facets_distribution); + pub fn with_facets<'b>(&'b mut self, facets: Selectors<&'a [&'a str]>) -> &'b mut Query<'a> { + self.facets = Some(facets); self } pub fn with_sort<'b>(&'b mut self, sort: &'a [&'a str]) -> &'b mut Query<'a> { @@ -310,8 +325,11 @@ impl<'a> Query<'a> { self.highlight_post_tag = Some(highlight_post_tag); self } - pub fn with_matches<'b>(&'b mut self, matches: bool) -> &'b mut Query<'a> { - self.matches = Some(matches); + pub fn with_show_matches_position<'b>( + &'b mut self, + show_matches_position: bool, + ) -> &'b mut Query<'a> { + self.show_matches_position = Some(show_matches_position); self } pub fn build(&mut self) -> Query<'a> { @@ -377,6 +395,19 @@ mod tests { Ok(()) } + #[meilisearch_test] + async fn test_query_builder(_client: Client, index: Index) -> Result<(), Error> { + let mut query = Query::new(&index); + query.with_query("space").with_offset(42).with_limit(21); + + let res = query.execute::().await.unwrap(); + + assert_eq!(res.query, "space".to_string()); + assert_eq!(res.limit, 21); + assert_eq!(res.offset, 42); + Ok(()) + } + #[meilisearch_test] async fn test_query_string(client: Client, index: Index) -> Result<(), Error> { setup_test_index(&client, &index).await?; @@ -451,11 +482,11 @@ mod tests { setup_test_index(&client, &index).await?; let mut query = Query::new(&index); - query.with_facets_distribution(Selectors::All); + query.with_facets(Selectors::All); let results: SearchResults = index.execute_query(&query).await?; assert_eq!( results - .facets_distribution + .facet_distribution .unwrap() .get("kind") .unwrap() @@ -465,11 +496,11 @@ mod tests { ); let mut query = Query::new(&index); - query.with_facets_distribution(Selectors::Some(&["kind"])); + query.with_facets(Selectors::Some(&["kind"])); let results: SearchResults = index.execute_query(&query).await?; assert_eq!( results - .facets_distribution + .facet_distribution .clone() .unwrap() .get("kind") @@ -480,7 +511,7 @@ mod tests { ); assert_eq!( results - .facets_distribution + .facet_distribution .unwrap() .get("kind") .unwrap() @@ -690,17 +721,17 @@ mod tests { } #[meilisearch_test] - async fn test_query_matches(client: Client, index: Index) -> Result<(), Error> { + async fn test_query_show_matches_position(client: Client, index: Index) -> Result<(), Error> { setup_test_index(&client, &index).await?; let mut query = Query::new(&index); query.with_query("dolor text"); - query.with_matches(true); + query.with_show_matches_position(true); let results: SearchResults = index.execute_query(&query).await?; - assert_eq!(results.hits[0].matches_info.as_ref().unwrap().len(), 2); + assert_eq!(results.hits[0].matches_position.as_ref().unwrap().len(), 2); assert_eq!( results.hits[0] - .matches_info + .matches_position .as_ref() .unwrap() .get("value") @@ -720,6 +751,7 @@ mod tests { let mut query = Query::new(&index); query.with_query("harry \"of Fire\""); let results: SearchResults = index.execute_query(&query).await?; + assert_eq!(results.hits.len(), 1); Ok(()) } From 0150f1cd1da132676c0557df6a0786ae82060328 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:38:16 +0200 Subject: [PATCH 08/19] Update documents for v0.28.0 (#317) * Update documents api v0.28.0 --- src/documents.rs | 232 +++++++++++++++++++++++++++++++++++++++++++++++ src/indexes.rs | 148 ++++++++++++++++++++++-------- src/lib.rs | 2 + 3 files changed, 343 insertions(+), 39 deletions(-) create mode 100644 src/documents.rs diff --git a/src/documents.rs b/src/documents.rs new file mode 100644 index 00000000..8fd593d4 --- /dev/null +++ b/src/documents.rs @@ -0,0 +1,232 @@ +use crate::{errors::Error, indexes::Index}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize)] +pub struct DocumentsResults { + pub results: Vec, + pub limit: u32, + pub offset: u32, + pub total: u32, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DocumentsQuery<'a> { + #[serde(skip_serializing)] + pub index: &'a Index, + + /// The number of documents to skip. + /// If the value of the parameter `offset` is `n`, the `n` first documents will not be returned. + /// This is helpful for pagination. + /// + /// Example: If you want to skip the first document, set offset to `1`. + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option, + + /// The maximum number of documents returned. + /// If the value of the parameter `limit` is `n`, there will never be more than `n` documents in the response. + /// This is helpful for pagination. + /// + /// Example: If you don't want to get more than two documents, set limit to `2`. + /// Default: `20` + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + + /// The fields that should appear in the documents. By default all of the fields are present. + #[serde(skip_serializing_if = "Option::is_none")] + pub fields: Option>, +} + +impl<'a> DocumentsQuery<'a> { + pub fn new(index: &Index) -> DocumentsQuery { + DocumentsQuery { + index, + offset: None, + limit: None, + fields: None, + } + } + + /// Specify the offset. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let index = client.index("my_index"); + /// + /// let mut documents_query = DocumentsQuery::new(&index); + /// + /// documents_query.with_offset(1); + /// ``` + pub fn with_offset(&mut self, offset: usize) -> &mut DocumentsQuery<'a> { + self.offset = Some(offset); + self + } + + /// Specify the limit. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let index = client.index("my_index"); + /// + /// let mut documents_query = DocumentsQuery::new(&index); + /// + /// documents_query.with_limit(1); + /// ``` + pub fn with_limit(&mut self, limit: usize) -> &mut DocumentsQuery<'a> { + self.limit = Some(limit); + self + } + + /// Specify the fields to return in the documents. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let index = client.index("my_index"); + /// + /// let mut documents_query = DocumentsQuery::new(&index); + /// + /// documents_query.with_fields(["title"]); + /// ``` + pub fn with_fields( + &mut self, + fields: impl IntoIterator, + ) -> &mut DocumentsQuery<'a> { + self.fields = Some(fields.into_iter().collect()); + self + } + + /// Execute the get documents query. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # use serde::{Deserialize, Serialize}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// + /// # futures::executor::block_on(async move { + /// # let index = client.create_index("documents_query_execute", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); + /// #[derive(Debug, Serialize, Deserialize, PartialEq)] + /// struct MyObject { + /// id: Option, + /// kind: String, + /// } + /// let index = client.index("documents_query_execute"); + /// + /// let mut documents_query = DocumentsQuery::new(&index); + /// + /// documents_query.with_offset(1).execute::().await.unwrap(); + /// # }); + /// ``` + pub async fn execute( + &self, + ) -> Result, Error> { + self.index.get_documents_with::(self).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{client::*, indexes::*}; + use meilisearch_test_macro::meilisearch_test; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObject { + id: Option, + kind: String, + } + + async fn setup_test_index(client: &Client, index: &Index) -> Result<(), Error> { + let t0 = index + .add_documents( + &[ + MyObject { + id: Some(0), + kind: "text".into(), + }, + MyObject { + id: Some(1), + kind: "text".into(), + }, + MyObject { + id: Some(2), + kind: "title".into(), + }, + MyObject { + id: Some(3), + kind: "title".into(), + }, + ], + None, + ) + .await?; + + t0.wait_for_completion(client, None, None).await?; + + Ok(()) + } + + #[meilisearch_test] + async fn test_get_documents_with_execute(client: Client, index: Index) -> Result<(), Error> { + setup_test_index(&client, &index).await?; + // let documents = index.get_documents(None, None, None).await.unwrap(); + let documents = DocumentsQuery::new(&index) + .with_limit(1) + .with_offset(1) + .with_fields(["kind"]) + .execute::() + .await + .unwrap(); + + assert_eq!(documents.limit, 1); + assert_eq!(documents.offset, 1); + assert_eq!(documents.results.len(), 1); + + Ok(()) + } + #[meilisearch_test] + async fn test_get_documents_with_only_one_param( + client: Client, + index: Index, + ) -> Result<(), Error> { + setup_test_index(&client, &index).await?; + // let documents = index.get_documents(None, None, None).await.unwrap(); + let documents = DocumentsQuery::new(&index) + .with_limit(1) + .execute::() + .await + .unwrap(); + + assert_eq!(documents.limit, 1); + assert_eq!(documents.offset, 0); + assert_eq!(documents.results.len(), 1); + + Ok(()) + } +} diff --git a/src/indexes.rs b/src/indexes.rs index 959686fb..633bcc1b 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -1,4 +1,12 @@ -use crate::{client::Client, errors::Error, request::*, search::*, task_info::TaskInfo, tasks::*}; +use crate::{ + client::Client, + documents::{DocumentsQuery, DocumentsResults}, + errors::Error, + request::*, + search::*, + task_info::TaskInfo, + tasks::*, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::HashMap, fmt::Display, time::Duration}; use time::OffsetDateTime; @@ -315,11 +323,6 @@ impl Index { /// Get [Document]s by batch. /// - /// Using the optional parameters offset and limit, you can browse through all your documents. - /// If None, offset will be set to 0, limit to 20, and all attributes will be retrieved. - /// - /// *Note: Documents are ordered by Meilisearch depending on the hash of their id.* - /// /// # Example /// /// ``` @@ -346,34 +349,67 @@ impl Index { /// # movie_index.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// /// // retrieve movies (you have to put some movies in the index before) - /// let movies = movie_index.get_documents::(None, None, None).await.unwrap(); + /// let movies = movie_index.get_documents::().await.unwrap(); /// - /// assert!(movies.len() > 0); + /// assert!(movies.results.len() > 0); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` pub async fn get_documents( &self, - offset: Option, - limit: Option, - attributes_to_retrieve: Option<&str>, - ) -> Result, Error> { - let mut url = format!("{}/indexes/{}/documents?", self.client.host, self.uid); - if let Some(offset) = offset { - url.push_str("offset="); - url.push_str(offset.to_string().as_str()); - url.push('&'); - } - if let Some(limit) = limit { - url.push_str("limit="); - url.push_str(limit.to_string().as_str()); - url.push('&'); - } - if let Some(attributes_to_retrieve) = attributes_to_retrieve { - url.push_str("attributesToRetrieve="); - url.push_str(attributes_to_retrieve); - } - request::<(), Vec>(&url, &self.client.api_key, Method::Get(()), 200).await + ) -> Result, Error> { + let url = format!("{}/indexes/{}/documents", self.client.host, self.uid); + + request::<(), DocumentsResults>(&url, &self.client.api_key, Method::Get(()), 200).await + } + /// Get [Document]s by batch with parameters. + /// + /// # Example + /// + /// ``` + /// use serde::{Serialize, Deserialize}; + /// + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// + /// #[derive(Serialize, Deserialize, Debug)] + /// # #[derive(PartialEq)] + /// struct Movie { + /// name: String, + /// description: String, + /// } + /// + /// + /// # futures::executor::block_on(async move { + /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let movie_index = client.index("get_documents"); + /// + /// # movie_index.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// + /// let mut query = DocumentsQuery::new(&movie_index); + /// query.with_limit(1); + /// // retrieve movies (you have to put some movies in the index before) + /// let movies = movie_index.get_documents_with::(&query).await.unwrap(); + /// + /// assert!(movies.results.len() == 1); + /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + /// ``` + pub async fn get_documents_with( + &self, + documents_query: &DocumentsQuery<'_>, + ) -> Result, Error> { + let url = format!("{}/indexes/{}/documents", self.client.host, self.uid); + request::<&DocumentsQuery, DocumentsResults>( + &url, + &self.client.api_key, + Method::Get(documents_query), + 200, + ) + .await } /// Add a list of [Document]s or replace them if they already exist. @@ -425,7 +461,7 @@ impl Index { /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed /// client.wait_for_task(task, None, None).await.unwrap(); /// - /// let movies = movie_index.get_documents::(None, None, None).await.unwrap(); + /// let movies = movie_index.get_documents::().await.unwrap(); /// assert!(movies.len() >= 3); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); @@ -503,7 +539,7 @@ impl Index { /// // Meilisearch may take some time to execute the request so we are going to wait till it's completed /// client.wait_for_task(task, None, None).await.unwrap(); /// - /// let movies = movie_index.get_documents::(None, None, None).await.unwrap(); + /// let movies = movie_index.get_documents::().await.unwrap(); /// assert!(movies.len() >= 3); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); @@ -558,8 +594,8 @@ impl Index { /// .wait_for_completion(&client, None, None) /// .await /// .unwrap(); - /// let movies = movie_index.get_documents::(None, None, None).await.unwrap(); - /// assert_eq!(movies.len(), 0); + /// let movies = movie_index.get_documents::().await.unwrap(); + /// assert_eq!(movies.results.len(), 0); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` @@ -977,8 +1013,8 @@ impl Index { /// /// client.wait_for_task(tasks.last().unwrap(), None, None).await.unwrap(); /// - /// let movies = movie_index.get_documents::(None, None, None).await.unwrap(); - /// assert!(movies.len() >= 3); + /// let movies = movie_index.get_documents::().await.unwrap(); + /// assert!(movies.results.len() >= 3); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, /// None).await.unwrap(); /// # }); @@ -1042,8 +1078,8 @@ impl Index { /// /// client.wait_for_task(tasks.last().unwrap(), None, None).await.unwrap(); /// - /// let movies = movie_index.get_documents::(None, None, None).await.unwrap(); - /// assert!(movies.len() >= 3); + /// let movies = movie_index.get_documents::().await.unwrap(); + /// assert!(movies.results.len() >= 3); /// /// let updated_movies = [ /// Movie { @@ -1064,10 +1100,10 @@ impl Index { /// /// client.wait_for_task(tasks.last().unwrap(), None, None).await.unwrap(); /// - /// let movies_updated = movie_index.get_documents::(None, None, None).await.unwrap(); - /// assert!(movies_updated.len() >= 3); + /// let movies_updated = movie_index.get_documents::().await.unwrap(); + /// assert!(movies_updated.results.len() >= 3); /// - /// assert!(&movies_updated[..] == &updated_movies[..]); + /// assert!(&movies_updated.results[..] == &updated_movies[..]); /// /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, /// None).await.unwrap(); @@ -1481,6 +1517,40 @@ mod tests { assert!(index.primary_key.is_none()); } + #[meilisearch_test] + async fn test_get_documents(index: Index) { + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Object { + id: usize, + value: String, + kind: String, + } + let res = index.get_documents::().await.unwrap(); + + assert_eq!(res.limit, 20) + } + + #[meilisearch_test] + async fn test_get_documents_with(index: Index) { + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Object { + id: usize, + value: String, + kind: String, + } + + let mut documents_query = DocumentsQuery::new(&index); + documents_query.with_limit(1).with_offset(2); + + let res = index + .get_documents_with::(&documents_query) + .await + .unwrap(); + + assert_eq!(res.limit, 1); + assert_eq!(res.offset, 2); + } + #[meilisearch_test] async fn test_get_one_task(client: Client, index: Index) -> Result<(), Error> { let task = index diff --git a/src/lib.rs b/src/lib.rs index 21bab82d..61b0fa1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -225,6 +225,8 @@ /// Module containing the [client::Client] struct. pub mod client; +/// Module representing the [documents] structures. +pub mod documents; /// Module containing the [document::Document] trait. pub mod dumps; /// Module containing the [errors::Error] struct. From 70f5424f2377c04f9bcaa7ec1690cd589cf3cf8e Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:41:35 +0200 Subject: [PATCH 09/19] Update get one document for v0.28.0 (#323) * Update get one document for v0.28.0 * Use struct instead of query creation on get_document parameters * Show partialEq on Movie struct --- src/documents.rs | 7 +++++++ src/indexes.rs | 44 ++++++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/documents.rs b/src/documents.rs index 8fd593d4..5dfc2a3a 100644 --- a/src/documents.rs +++ b/src/documents.rs @@ -9,6 +9,13 @@ pub struct DocumentsResults { pub total: u32, } +#[derive(Debug, Clone, Serialize)] +pub struct DocumentQuery<'a> { + /// The fields that should appear in the documents. By default all of the fields are present. + #[serde(skip_serializing_if = "Option::is_none")] + pub fields: Option>, +} + #[derive(Debug, Clone, Serialize)] pub struct DocumentsQuery<'a> { #[serde(skip_serializing)] diff --git a/src/indexes.rs b/src/indexes.rs index 633bcc1b..106da0e3 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -1,6 +1,6 @@ use crate::{ client::Client, - documents::{DocumentsQuery, DocumentsResults}, + documents::{DocumentQuery, DocumentsQuery, DocumentsResults}, errors::Error, request::*, search::*, @@ -285,40 +285,48 @@ impl Index { /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # - /// #[derive(Serialize, Deserialize, Debug)] - /// # #[derive(PartialEq)] + /// #[derive(Serialize, Debug)] /// struct Movie { /// name: String, /// description: String, + /// age: Option + /// } + /// + /// #[derive(Deserialize, Debug, PartialEq)] + /// struct ReturnedMovie { + /// name: String, + /// description: String /// } /// /// /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// let movies = client.index("get_document"); - /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # 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."), age: Some(1)}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// /// // retrieve a document (you have to put the document in the index before) - /// let interstellar = movies.get_document::("Interstellar").await.unwrap(); + /// let interstellar = movies.get_document::("Interstellar", Some(["name", "description"].to_vec())).await.unwrap(); /// - /// assert_eq!(interstellar, Movie { + /// assert_eq!(interstellar, ReturnedMovie { /// 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.") + /// 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."), /// }); /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` - pub async fn get_document(&self, uid: &str) -> Result { - request::<(), T>( - &format!( - "{}/indexes/{}/documents/{}", - self.client.host, self.uid, uid - ), - &self.client.api_key, - Method::Get(()), - 200, - ) - .await + pub async fn get_document( + &self, + document_id: &str, + fields: Option>, + ) -> Result { + let url = format!( + "{}/indexes/{}/documents/{}", + self.client.host, self.uid, document_id + ); + + let query = DocumentQuery { fields }; + + request::<&DocumentQuery, T>(&url, &self.client.api_key, Method::Get(&query), 200).await } /// Get [Document]s by batch. From 78f08a3e78abdf7b7c4f0ab0a092bc56a45bfcf6 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:46:16 +0200 Subject: [PATCH 10/19] Update tenant token api for v0.28.1 (#318) * Update tenant token api for v0.28.1 * Fix clippy warning * Remove public on TenantTokenClaim --- Cargo.toml | 1 + src/client.rs | 6 ++- src/errors.rs | 14 +++++- src/indexes.rs | 5 +- src/search.rs | 6 +-- src/tenant_tokens.rs | 114 ++++++++++++++++++++++++++++++------------- 6 files changed, 103 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 078f256d..9724a196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ serde_json = "1.0" time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing"] } jsonwebtoken = { version = "8", default-features = false } yaup = "0.2.0" +uuid = { version = "1.1.2", features = ["v4"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures = "0.3" diff --git a/src/client.rs b/src/client.rs index a7bcc1a9..15a605e4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -806,19 +806,21 @@ impl Client { /// # /// # futures::executor::block_on(async move { /// # let client = client::Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// let token = client.generate_tenant_token(serde_json::json!(["*"]), None, None).unwrap(); + /// let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string(); + /// let token = client.generate_tenant_token(api_key_uid, serde_json::json!(["*"]), None, None).unwrap(); /// let client = client::Client::new(MEILISEARCH_HOST, token); /// # }); /// ``` pub fn generate_tenant_token( &self, + api_key_uid: String, search_rules: serde_json::Value, api_key: Option<&str>, expires_at: Option, ) -> Result { let api_key = api_key.unwrap_or(&self.api_key); - crate::tenant_tokens::generate_tenant_token(search_rules, api_key, expires_at) + crate::tenant_tokens::generate_tenant_token(api_key_uid, search_rules, api_key, expires_at) } } diff --git a/src/errors.rs b/src/errors.rs index 68f78650..b1daf14e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -36,6 +36,10 @@ pub enum Error { HttpError(String), // The library formating the query parameters encountered an error. Yaup(yaup::Error), + // The library validating the format of an uuid. + Uuid(uuid::Error), + // Error thrown in case the version of the Uuid is not v4. + InvalidUuid4Version, } #[derive(Debug, Clone, Deserialize)] @@ -75,6 +79,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: uuid::Error) -> Error { + Error::Uuid(error) + } +} + /// The type of error that was encountered. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -194,7 +204,9 @@ impl std::fmt::Display for Error { Error::TenantTokensInvalidApiKey => write!(fmt, "The provided api_key is invalid."), Error::TenantTokensExpiredSignature => write!(fmt, "The provided expires_at is already expired."), Error::InvalidTenantToken(e) => write!(fmt, "Impossible to generate the token, jsonwebtoken encountered an error: {}", e), - Error::Yaup(e) => write!(fmt, "Internal Error: could not parse the query parameters: {}", e) + Error::Yaup(e) => write!(fmt, "Internal Error: could not parse the query parameters: {}", e), + Error::Uuid(e) => write!(fmt, "The uid of the token has bit an uuid4 format: {}", e), + Error::InvalidUuid4Version => write!(fmt, "The uid provided to the token is not of version uuidv4") } } } diff --git a/src/indexes.rs b/src/indexes.rs index 106da0e3..1309e8dd 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -370,10 +370,8 @@ impl Index { request::<(), DocumentsResults>(&url, &self.client.api_key, Method::Get(()), 200).await } + /// Get [Document]s by batch with parameters. - /// - /// # Example - /// /// ``` /// use serde::{Serialize, Deserialize}; /// @@ -409,7 +407,6 @@ impl Index { pub async fn get_documents_with( &self, documents_query: &DocumentsQuery<'_>, - ) -> Result, Error> { let url = format!("{}/indexes/{}/documents", self.client.host, self.uid); request::<&DocumentsQuery, DocumentsResults>( &url, diff --git a/src/search.rs b/src/search.rs index c1abaa07..67776cee 100644 --- a/src/search.rs +++ b/src/search.rs @@ -666,7 +666,6 @@ mod tests { query.with_highlight_post_tag(" ⊂(´• ω •`⊂)"); let results: SearchResults = index.execute_query(&query).await?; - dbg!(&results); assert_eq!( &Document { id: 2, @@ -784,9 +783,10 @@ mod tests { for rules in search_rules { let token = allowed_client - .generate_tenant_token(rules, None, None) + .generate_tenant_token(key.uid.clone(), rules, None, None) .expect("Cannot generate tenant token."); - let new_client = Client::new(meilisearch_host, token); + + let new_client = Client::new(meilisearch_host, token.clone()); let result: SearchResults = new_client .index(index.uid.to_string()) diff --git a/src/tenant_tokens.rs b/src/tenant_tokens.rs index 369f401b..83fc3704 100644 --- a/src/tenant_tokens.rs +++ b/src/tenant_tokens.rs @@ -1,34 +1,41 @@ -use crate::{ - errors::* -}; -use serde::{Serialize, Deserialize}; -use jsonwebtoken::{encode, Header, EncodingKey}; -use time::{OffsetDateTime}; +use crate::errors::*; +use jsonwebtoken::{encode, EncodingKey, Header}; +use serde::{Deserialize, Serialize}; use serde_json::Value; +use time::OffsetDateTime; +use uuid::Uuid; #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "camelCase")] struct TenantTokenClaim { - api_key_prefix: String, + api_key_uid: String, search_rules: Value, #[serde(with = "time::serde::timestamp::option")] exp: Option, } -pub fn generate_tenant_token(search_rules: Value, api_key: impl AsRef, expires_at: Option) -> Result { - if api_key.as_ref().chars().count() < 8 { - return Err(Error::TenantTokensInvalidApiKey) +pub fn generate_tenant_token( + api_key_uid: String, + search_rules: Value, + api_key: impl AsRef, + expires_at: Option, +) -> Result { + // Validate uuid format + let uid = Uuid::try_parse(&api_key_uid)?; + + // Validate uuid version + if uid.get_version_num() != 4 { + return Err(Error::InvalidUuid4Version); } if expires_at.map_or(false, |expires_at| OffsetDateTime::now_utc() > expires_at) { - return Err(Error::TenantTokensExpiredSignature) + return Err(Error::TenantTokensExpiredSignature); } - let key_prefix = api_key.as_ref().chars().take(8).collect(); let claims = TenantTokenClaim { - api_key_prefix: key_prefix, + api_key_uid, exp: expires_at, - search_rules + search_rules, }; let token = encode( @@ -42,9 +49,9 @@ pub fn generate_tenant_token(search_rules: Value, api_key: impl AsRef, expi #[cfg(test)] mod tests { - use serde_json::json; use crate::tenant_tokens::*; - use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm}; + use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; + use serde_json::json; use std::collections::HashSet; const SEARCH_RULES: [&str; 1] = ["*"]; @@ -60,13 +67,19 @@ mod tests { #[test] fn test_generate_token_with_given_key() { - let token = generate_tenant_token(json!(SEARCH_RULES), VALID_KEY, None).unwrap(); + let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string(); + let token = + generate_tenant_token(api_key_uid, json!(SEARCH_RULES), VALID_KEY, None).unwrap(); let valid_key = decode::( - &token, &DecodingKey::from_secret(VALID_KEY.as_ref()), &build_validation() + &token, + &DecodingKey::from_secret(VALID_KEY.as_ref()), + &build_validation(), ); let invalid_key = decode::( - &token, &DecodingKey::from_secret("not-the-same-key".as_ref()), &build_validation() + &token, + &DecodingKey::from_secret("not-the-same-key".as_ref()), + &build_validation(), ); assert!(valid_key.is_ok()); @@ -74,20 +87,25 @@ mod tests { } #[test] - fn test_generate_token_without_key() { + fn test_generate_token_without_uid() { + let api_key_uid = "".to_string(); let key = String::from(""); - let token = generate_tenant_token(json!(SEARCH_RULES), &key, None); + let token = generate_tenant_token(api_key_uid, json!(SEARCH_RULES), &key, None); assert!(token.is_err()); } #[test] fn test_generate_token_with_expiration() { + let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string(); let exp = OffsetDateTime::now_utc() + time::Duration::HOUR; - let token = generate_tenant_token(json!(SEARCH_RULES), VALID_KEY, Some(exp)).unwrap(); + let token = + generate_tenant_token(api_key_uid, json!(SEARCH_RULES), VALID_KEY, Some(exp)).unwrap(); let decoded = decode::( - &token, &DecodingKey::from_secret(VALID_KEY.as_ref()), &Validation::new(Algorithm::HS256) + &token, + &DecodingKey::from_secret(VALID_KEY.as_ref()), + &Validation::new(Algorithm::HS256), ); assert!(decoded.is_ok()); @@ -95,33 +113,63 @@ mod tests { #[test] fn test_generate_token_with_expires_at_in_the_past() { + let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string(); let exp = OffsetDateTime::now_utc() - time::Duration::HOUR; - let token = generate_tenant_token(json!(SEARCH_RULES), VALID_KEY, Some(exp)); + let token = generate_tenant_token(api_key_uid, json!(SEARCH_RULES), VALID_KEY, Some(exp)); assert!(token.is_err()); } #[test] fn test_generate_token_contains_claims() { - let token = generate_tenant_token(json!(SEARCH_RULES), VALID_KEY, None).unwrap(); + let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string(); + let token = + generate_tenant_token(api_key_uid.clone(), json!(SEARCH_RULES), VALID_KEY, None) + .unwrap(); let decoded = decode::( - &token, &DecodingKey::from_secret(VALID_KEY.as_ref()), &build_validation() - ).expect("Cannot decode the token"); + &token, + &DecodingKey::from_secret(VALID_KEY.as_ref()), + &build_validation(), + ) + .expect("Cannot decode the token"); - assert_eq!(decoded.claims.api_key_prefix, &VALID_KEY[..8]); + assert_eq!(decoded.claims.api_key_uid, api_key_uid); assert_eq!(decoded.claims.search_rules, json!(SEARCH_RULES)); } #[test] fn test_generate_token_with_multi_byte_chars() { + let api_key_uid = "76cf8b87-fd12-4688-ad34-260d930ca4f4".to_string(); let key = "Ëa1ทt9bVcL-vãUทtP3OpXW5qPc%bWH5ทvw09"; - let token = generate_tenant_token(json!(SEARCH_RULES), key, None).unwrap(); + let token = + generate_tenant_token(api_key_uid.clone(), json!(SEARCH_RULES), key, None).unwrap(); let decoded = decode::( - &token, &DecodingKey::from_secret(key.as_ref()), &build_validation() - ).expect("Cannot decode the token"); + &token, + &DecodingKey::from_secret(key.as_ref()), + &build_validation(), + ) + .expect("Cannot decode the token"); + + assert_eq!(decoded.claims.api_key_uid, api_key_uid); + } + + #[test] + fn test_generate_token_with_wrongly_formated_uid() { + let api_key_uid = "xxx".to_string(); + let key = "Ëa1ทt9bVcL-vãUทtP3OpXW5qPc%bWH5ทvw09"; + let token = generate_tenant_token(api_key_uid.clone(), json!(SEARCH_RULES), key, None); - assert_eq!(decoded.claims.api_key_prefix, "Ëa1ทt9bV"); + assert!(token.is_err()); + } + + #[test] + fn test_generate_token_with_wrong_uid_version() { + let api_key_uid = "6a11eb96-2485-11ed-861d-0242ac120002".to_string(); + let key = "Ëa1ทt9bVcL-vãUทtP3OpXW5qPc%bWH5ทvw09"; + let token = generate_tenant_token(api_key_uid.clone(), json!(SEARCH_RULES), key, None); + + assert!(token.is_err()); } } From 6c1184299fe055ea615da8897436475ef37d3dc1 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:48:54 +0200 Subject: [PATCH 11/19] Update dumps for v0.28.0 (#319) * Update dump api for v0.28.0 --- .code-samples.meilisearch.yaml | 2 - src/dumps.rs | 145 +++++++++++---------------------- src/tasks.rs | 9 ++ 3 files changed, 58 insertions(+), 98 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index ca109c57..9ed8b192 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -668,8 +668,6 @@ faceted_search_walkthrough_filter_1: |- .unwrap(); post_dump_1: |- client.create_dump().await.unwrap(); -get_dump_status_1: |- - client.get_dump_status("20201101-110357260").await.unwrap(); phrase_search_1: |- let results: SearchResults = client.index("movies") .search() diff --git a/src/dumps.rs b/src/dumps.rs index 28ff66b8..67b9e63b 100644 --- a/src/dumps.rs +++ b/src/dumps.rs @@ -15,7 +15,7 @@ //! # Example //! //! ```no_run -//! # use meilisearch_sdk::{client::*, errors::*, dumps::*}; +//! # use meilisearch_sdk::{client::*, errors::*, dumps::*, dumps::*, task_info::*, tasks::*}; //! # use futures_await_test::async_test; //! # use std::{thread::sleep, time::Duration}; //! # futures::executor::block_on(async move { @@ -26,46 +26,18 @@ //! let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); //! //! // Create a dump -//! let dump_info = client.create_dump().await.unwrap(); -//! assert!(matches!(dump_info.status, DumpStatus::InProgress)); -//! -//! // Wait for Meilisearch to proceed -//! sleep(Duration::from_secs(5)); -//! -//! // Check the status of the dump -//! let dump_info = client.get_dump_status(&dump_info.uid).await.unwrap(); -//! assert!(matches!(dump_info.status, DumpStatus::Done)); +//! let task_info = client.create_dump().await.unwrap(); +//! assert!(matches!( +//! task_info, +//! TaskInfo { +//! update_type: TaskType::DumpCreation { .. }, +//! .. +//! } +//!)); //! # }); //! ``` -use crate::{client::Client, errors::Error, request::*}; -use serde::Deserialize; - -/// The status of a dump.\ -/// Contained in [`DumpInfo`]. -#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum DumpStatus { - /// Dump creation is in progress. - Done, - /// Dump creation is in progress. - InProgress, - /// An error occured during dump process, and the task was aborted. - Failed, -} - -/// Limited informations about a dump.\ -/// Can be obtained with [create_dump](Client::create_dump) and [get_dump_status](Client::get_dump_status) methods. -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DumpInfo { - pub uid: String, - pub status: DumpStatus, - pub error: Option, - pub started_at: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub finished_at: Option, -} +use crate::{client::Client, errors::Error, request::*, task_info::TaskInfo}; /// Dump related methods.\ /// See the [dumps](crate::dumps) module. @@ -77,7 +49,7 @@ impl Client { /// # Example /// /// ```no_run - /// # use meilisearch_sdk::{client::*, errors::*, dumps::*}; + /// # use meilisearch_sdk::{client::*, errors::*, dumps::*, dumps::*, task_info::*, tasks::*}; /// # use futures_await_test::async_test; /// # use std::{thread::sleep, time::Duration}; /// # futures::executor::block_on(async move { @@ -87,12 +59,18 @@ impl Client { /// # /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// # - /// let dump_info = client.create_dump().await.unwrap(); - /// assert!(matches!(dump_info.status, DumpStatus::InProgress)); + /// let task_info = client.create_dump().await.unwrap(); + /// assert!(matches!( + /// task_info, + /// TaskInfo { + /// update_type: TaskType::DumpCreation { .. }, + /// .. + /// } + /// )); /// # }); /// ``` - pub async fn create_dump(&self) -> Result { - request::<(), DumpInfo>( + pub async fn create_dump(&self) -> Result { + request::<(), TaskInfo>( &format!("{}/dumps", self.host), &self.api_key, Method::Post(()), @@ -100,72 +78,47 @@ impl Client { ) .await } - - /// Get the status of a dump creation process using [the uid](DumpInfo::uid) returned after calling the [dump creation method](Client::create_dump). - /// - /// # Example - /// - /// ```no_run - /// # use meilisearch_sdk::{client::*, errors::*, dumps::*}; - /// # use futures_await_test::async_test; - /// # use std::{thread::sleep, time::Duration}; - /// # - /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); - /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); - /// # - /// # futures::executor::block_on(async move { - /// # - /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); - /// # let dump_info = client.create_dump().await.unwrap(); - /// # sleep(Duration::from_secs(5)); - /// # - /// let dump_info = client.get_dump_status(&dump_info.uid).await.unwrap(); - /// # }); - /// ``` - pub async fn get_dump_status(&self, dump_uid: impl AsRef) -> Result { - request::<(), DumpInfo>( - &format!("{}/dumps/{}/status", self.host, dump_uid.as_ref()), - &self.api_key, - Method::Get(()), - 200, - ) - .await - } } /// Alias for [create_dump](Client::create_dump). -pub async fn create_dump(client: &Client) -> Result { +pub async fn create_dump(client: &Client) -> Result { client.create_dump().await } -/// Alias for [get_dump_status](Client::get_dump_status). -pub async fn get_dump_status( - client: &Client, - dump_uid: impl AsRef, -) -> Result { - client.get_dump_status(dump_uid).await -} - #[cfg(test)] mod tests { use super::*; - use crate::client::*; + use crate::{client::*, tasks::*}; use meilisearch_test_macro::meilisearch_test; - use std::{thread::sleep, time::Duration}; + use std::time::Duration; #[meilisearch_test] - async fn test_dumps(client: Client) { - // Create a dump - let dump_info = client.create_dump().await.unwrap(); - assert!(matches!(dump_info.status, DumpStatus::InProgress)); + async fn test_dumps_success_creation(client: Client) -> Result<(), Error> { + let task = client + .create_dump() + .await? + .wait_for_completion( + &client, + Some(Duration::from_millis(1)), + Some(Duration::from_millis(6000)), + ) + .await?; + + assert!(matches!(task, Task::Succeeded { .. })); + Ok(()) + } - // Wait for Meilisearch to do the dump - sleep(Duration::from_secs(5)); + #[meilisearch_test] + async fn test_dumps_correct_update_type(client: Client) -> Result<(), Error> { + let task_info = client.create_dump().await.unwrap(); - // Assert that the dump was successful - let new_dump_info = client.get_dump_status(&dump_info.uid).await.unwrap(); - assert!(matches!(new_dump_info.status, DumpStatus::Done)); - assert!(new_dump_info.finished_at.is_some()); - assert!(new_dump_info.started_at.is_some()); + assert!(matches!( + task_info, + TaskInfo { + update_type: TaskType::DumpCreation { .. }, + .. + } + )); + Ok(()) } } diff --git a/src/tasks.rs b/src/tasks.rs index 26c470bf..e62fc60f 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -28,6 +28,9 @@ pub enum TaskType { SettingsUpdate { details: Option, }, + DumpCreation { + details: Option, + }, } #[derive(Debug, Clone, Deserialize)] @@ -69,6 +72,12 @@ pub struct IndexDeletion { pub deleted_documents: Option, } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DumpCreation { + pub dump_uid: Option, +} + #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct FailedTask { From e63cfea8cfc201d376adeebc7363cc2fa427eecc Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:51:59 +0200 Subject: [PATCH 12/19] Update crop marker for v0.28.0 (#321) * Update crop marker for v0.28.0 --- src/search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.rs b/src/search.rs index 67776cee..219310a5 100644 --- a/src/search.rs +++ b/src/search.rs @@ -641,7 +641,7 @@ mod tests { assert_eq!( &Document { id: 0, - value: "(ꈍᴗꈍ)consectetur adipiscing elit, sed do eiusmod(ꈍᴗꈍ)".to_string(), + value: "(ꈍᴗꈍ) sed do eiusmod tempor incididunt ut(ꈍᴗꈍ)".to_string(), kind: "text".to_string(), nested: Nested { child: "first".to_string() From a4da3d477faa93506d7f12d3591d8a1a66e7ed6c Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 29 Aug 2022 17:34:10 +0200 Subject: [PATCH 13/19] Fix failing test for v0.28.0 (#322) * Fix some forgotton tests for v0.28.0 * Remove unecessary comment * Fix tests * Fix closing brackets --- src/client.rs | 11 +++++------ src/indexes.rs | 5 +++-- src/tasks.rs | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/client.rs b/src/client.rs index 15a605e4..348b2217 100644 --- a/src/client.rs +++ b/src/client.rs @@ -481,9 +481,10 @@ impl Client { /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// # let key = client.get_keys().await.unwrap().results.into_iter() - /// .find(|k| k.name.as_ref().map_or(false, |name| name.starts_with("Default Search API Key"))); - /// let key_id = key.unwrap().key; // enter your API key here, for the example we use the search API key. - /// let key = client.get_key(key_id).await.unwrap(); + /// .find(|k| k.name.as_ref().map_or(false, |name| name.starts_with("Default Search API Key"))) + /// .unwrap(); + /// + /// let key = client.get_key(key).await.unwrap(); /// /// assert_eq!(key.name, Some("Default Search API Key".to_string())); /// # }); @@ -741,12 +742,10 @@ impl Client { /// # /// # futures::executor::block_on(async move { /// # let client = client::Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// /// let mut query = tasks::TasksQuery::new(&client); /// query.with_index_uid(["get_tasks_with"]); /// let tasks = client.get_tasks_with(&query).await.unwrap(); - /// - /// # assert!(tasks.results.len() > 0); - /// # client.index("get_tasks_with").delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` pub async fn get_tasks_with( diff --git a/src/indexes.rs b/src/indexes.rs index 1309e8dd..8570e3e0 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -407,6 +407,7 @@ impl Index { pub async fn get_documents_with( &self, documents_query: &DocumentsQuery<'_>, + ) -> Result, Error> { let url = format!("{}/indexes/{}/documents", self.client.host, self.uid); request::<&DocumentsQuery, DocumentsResults>( &url, @@ -467,7 +468,7 @@ impl Index { /// client.wait_for_task(task, None, None).await.unwrap(); /// /// let movies = movie_index.get_documents::().await.unwrap(); - /// assert!(movies.len() >= 3); + /// assert!(movies.results.len() >= 3); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` @@ -545,7 +546,7 @@ impl Index { /// client.wait_for_task(task, None, None).await.unwrap(); /// /// let movies = movie_index.get_documents::().await.unwrap(); - /// assert!(movies.len() >= 3); + /// assert!(movies.results.len() >= 3); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` diff --git a/src/tasks.rs b/src/tasks.rs index e62fc60f..45418631 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -279,7 +279,6 @@ impl Task { /// # let task = client.create_index("unwrap_failure", None).await.unwrap(); /// # let index = client.wait_for_task(task, None, None).await.unwrap().try_make_index(&client).unwrap(); /// - /// // TODO: fails until http method are implemented /// let task = index.set_ranking_rules(["wrong_ranking_rule"]) /// .await /// .unwrap() From e963e3c9d11c043b3af5f6b37a9428e7990852fa Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:59:26 +0200 Subject: [PATCH 14/19] Code samples changes related to Meilisearch release (v0.28.0) (#298) * Update README.md * Update README.tpl * Update .code-samples.meilisearch.yaml * First batch of code-samples * Add sample for get one document * Update .code-samples.meilisearch.yaml Co-authored-by: Bruno Casali * Fix get_one_document Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com> Co-authored-by: Charlotte Vermandel Co-authored-by: Bruno Casali --- .code-samples.meilisearch.yaml | 76 ++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 9ed8b192..447cb4ae 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -6,7 +6,9 @@ get_one_index_1: |- let movies: Index = client.get_index("movies").await.unwrap(); list_all_indexes_1: |- - let indexes: IndexesResults = client.list_all_indexes().await.unwrap(); + let mut indexes = IndexesQuery::new(&client) + .with_limit(3) + .execute().await.unwrap(); create_an_index_1: |- client.create_index("movies", Some("id")).await.unwrap(); update_an_index_1: |- @@ -14,7 +16,11 @@ update_an_index_1: |- delete_an_index_1: |- client.index("movies").delete().await.unwrap(); get_one_document_1: |- - let movie: Movie = client.index("movies").get_document(String::from("25684")).await.unwrap(); + let document_query = DocumentQuery::new(&index) + .with_fields(["id", "title", "poster", "release_date"]) + .execute::("25684") + .await + .unwrap() get_documents_1: |- let documents: Vec = client.index("movies").get_documents(None, Some(2), None).await.unwrap(); add_or_replace_documents_1: |- @@ -54,16 +60,40 @@ search_post_1: |- .execute() .await .unwrap(); -get_task_by_index_1: |- - let task: Task = client.index("movies").get_task(1).await.unwrap(); -get_all_tasks_by_index_1: |- - let tasks: TasksResults = client.index("movies").get_tasks().await.unwrap(); get_all_tasks_1: |- let tasks: TasksResults = client.get_tasks().await.unwrap(); +get_all_tasks_filtering_1: |- + let mut query = TasksQuery::new(&client) + .with_index_uid(["movies"]) + .execute() + .await + .unwrap(); +get_all_tasks_filtering_2: |- + let mut query = TasksQuery::new(&client) + .with_status(["succeeded", "failed"]) + .with_type(["documentAdditionOrUpdate"]) + .execute() + .await + .unwrap(); +get_all_tasks_paginating_1: |- + let mut query = TasksQuery::new(&client) + .with_limit(2) + .with_from(10) + .execute() + .await + .unwrap(); +get_all_tasks_paginating_2: |- + let mut query = TasksQuery::new(&client) + .with_limit(2) + .from(8) + .execute() + .await + .unwrap(); get_task_1: |- let task: Task = client.get_task(1).await.unwrap(); get_settings_1: |- let settings: Settings = client.index("movies").get_settings().await.unwrap(); +# Cannot be updated until API faceting and pagination are added update_settings_1: |- let mut synonyms = std::collections::HashMap::new(); synonyms.insert(String::from("wolverine"), vec!["xmen", "logan"]); @@ -303,7 +333,7 @@ search_parameter_guide_highlight_tag_1: |- // Get the formatted results let formatted_results: Vec<&Movie> = results.hits.iter().map(|r| r.formatted_result.as_ref().unwrap()).collect(); -search_parameter_guide_matches_1: |- +search_parameter_guide_show_matches_position_1: |- let results: SearchResults = client.index("movies").search() .with_query("winter feast") .with_show_matches_position(true) @@ -312,7 +342,7 @@ search_parameter_guide_matches_1: |- .unwrap(); // Get the matches info - let matched_info: Vec<&HashMap>> = results.hits.iter().map(|r| r.matches_position.as_ref().unwrap()).collect(); + let matches_position: Vec<&HashMap>> = results.hits.iter().map(|r| r.matches_position.as_ref().unwrap()).collect(); settings_guide_synonyms_1: |- let mut synonyms = HashMap::new(); synonyms.insert(String::from("sweater"), vec![String::from("jumper")]); @@ -564,7 +594,7 @@ getting_started_update_stop_words: |- let stop_words = ["the"]; client.index("movies").set_stop_words(&stop_words).await.unwrap(); getting_started_check_task_status: |- - client.index("movies").get_task(0).await.unwrap(); + client.get_task(0).await.unwrap(); getting_started_synonyms: |- let mut synonyms = std::collections::HashMap::new(); synonyms.insert(String::from("winnie"), vec![String::from("piglet")]); @@ -651,7 +681,7 @@ faceted_search_filter_1: |- .execute() .await .unwrap(); -faceted_search_facets_distribution_1: |- +faceted_search_facets_1: |- let results: SearchResults = client.index("movies").search() .with_query("Batman") .with_facets(Selectors::Some(&["genres"])) @@ -754,9 +784,9 @@ geosearch_guide_sort_usage_2: |- .await .unwrap(); get_one_key_1: |- - let key = client.get_key("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4").await.unwrap(); + let key = client.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d").await.unwrap(); get_all_keys_1: |- - let keys = client.get_keys().await.unwrap(); + let mut query = KeysQuery::new().with_limit(3).execute(&client).await.unwrap(); create_a_key_1: |- let mut key_options = KeyBuilder::new("Add documents: Products API key"); key_options.with_action(Action::DocumentsAdd) @@ -764,16 +794,16 @@ create_a_key_1: |- .with_index("products"); let new_key = client.create_key(key_options).await.unwrap(); update_a_key_1: |- - let mut key = client.get_key("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4").await.unwrap(); + let mut key = client.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d").await.unwrap(); key .with_description("Manage documents: Products/Reviews API key".to_string()) - .with_actions(vec![Action::DocumentsAdd, Action::DocumentsDelete]) - .with_indexes(vec!["products".to_string(), "reviews".to_string()]) - .with_expires_at(time::macros::datetime!(2042 - 04 - 02 00:42:42 UTC)) - .update(&client); + .with_name("Products/Reviews API key".to_string()) + .update(&client) + .await + .unwrap(); delete_a_key_1: |- - let key = client.get_key("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4").await.unwrap(); - client.delete_key(&key); + let key = client.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d").await.unwrap(); + client.delete_key(&key).await?; authorization_header_1: let client = Client::new("http://localhost:7700", "masterKey"); let keys = client.get_keys().await.unwrap(); @@ -782,8 +812,8 @@ security_guide_search_key_1: |- let result = client.index("patient_medical_records").search().execute().await.unwrap(); security_guide_update_key_1: |- let client = Client::new("http://localhost:7700", "masterKey"); - let mut key = client.get_key("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4").await.unwrap(); - key.with_indexes(vec!["doctors".to_string()]).update(&client); + let mut key = client.get_key("74c9c733-3368-4738-bbe5-1d18a5fecb37").await.unwrap(); + key.with_description("Default Search API key".to_string()).update(&client); security_guide_create_key_1: |- let client = Client::new("http://localhost:7700", "masterKey"); let mut key_options = KeyBuilder::new("Search patient records key"); @@ -796,8 +826,8 @@ security_guide_list_keys_1: |- let keys = client.get_keys().await.unwrap(); security_guide_delete_key_1: |- let client = Client::new("http://localhost:7700", "masterKey"); - let key = client.get_key("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4").await.unwrap(); - client.delete_key(&key); + let key = client.get_key("ac5cd97d-5a4b-4226-a868-2d0eb6d197ab").await.unwrap(); + client.delete_key(&key).await?; landing_getting_started_1: |- let client = Client::new("http://localhost:7700", "masterKey"); From ba0bb42c3819fb0432373dada044a499ba9e6d41 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 30 Aug 2022 15:19:16 +0200 Subject: [PATCH 15/19] Update code samples --- .code-samples.meilisearch.yaml | 51 ++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 447cb4ae..e1c6f345 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -8,21 +8,33 @@ get_one_index_1: |- list_all_indexes_1: |- let mut indexes = IndexesQuery::new(&client) .with_limit(3) - .execute().await.unwrap(); + .execute() + .await + .unwrap(); create_an_index_1: |- client.create_index("movies", Some("id")).await.unwrap(); update_an_index_1: |- - client.index("movies").update("movie_review_id").await.unwrap(); + let task = IndexUpdater::new("movies", &client) + .with_primary_key("movie_review_id") + .execute() + .await + .unwrap(); delete_an_index_1: |- client.index("movies").delete().await.unwrap(); get_one_document_1: |- - let document_query = DocumentQuery::new(&index) + let index = client.index("movies"); + let document = DocumentQuery::new(&index) .with_fields(["id", "title", "poster", "release_date"]) .execute::("25684") .await - .unwrap() + .unwrap(); get_documents_1: |- - let documents: Vec = client.index("movies").get_documents(None, Some(2), None).await.unwrap(); + let index = client.index("movies"); + let documents: DocumentsResults = DocumentsQuery::new(&index) + .with_limit(2) + .execute::() + .await + .unwrap(); add_or_replace_documents_1: |- let task: TaskInfo = client.index("movies").add_or_replace(&[ Movie { @@ -454,7 +466,11 @@ documents_guide_add_movie_1: |- } ], None).await.unwrap(); primary_field_guide_update_document_primary_key: |- - client.index("books").update("title").await.unwrap(); + let task = IndexUpdater::new("books", &client) + .with_primary_key("title") + .execute() + .await + .unwrap(); primary_field_guide_create_index_primary_key: |- client.create_index("books", Some("reference_number")).await.unwrap(); primary_field_guide_add_document_primary_key: |- @@ -786,12 +802,17 @@ geosearch_guide_sort_usage_2: |- get_one_key_1: |- let key = client.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d").await.unwrap(); get_all_keys_1: |- - let mut query = KeysQuery::new().with_limit(3).execute(&client).await.unwrap(); + let mut query = KeysQuery::new() + .with_limit(3) + .execute(&client) + .await + .unwrap(); create_a_key_1: |- let mut key_options = KeyBuilder::new("Add documents: Products API key"); - key_options.with_action(Action::DocumentsAdd) - .with_expires_at(time::macros::datetime!(2042 - 04 - 02 00:42:42 UTC)) - .with_index("products"); + key_options + .with_action(Action::DocumentsAdd) + .with_expires_at(time::macros::datetime!(2042 - 04 - 02 00:42:42 UTC)) + .with_index("products"); let new_key = client.create_key(key_options).await.unwrap(); update_a_key_1: |- let mut key = client.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d").await.unwrap(); @@ -817,9 +838,10 @@ security_guide_update_key_1: |- security_guide_create_key_1: |- let client = Client::new("http://localhost:7700", "masterKey"); let mut key_options = KeyBuilder::new("Search patient records key"); - key_options.with_action(Action::Search) - .with_expires_at(time::macros::datetime!(2023 - 01 - 01 00:00:00 UTC)) - .with_index("patient_medical_records"); + key_options + .with_action(Action::Search) + .with_expires_at(time::macros::datetime!(2023 - 01 - 01 00:00:00 UTC)) + .with_index("patient_medical_records"); let new_key = client.create_key(key_options).await.unwrap(); security_guide_list_keys_1: |- let client = Client::new("http://localhost:7700", "masterKey"); @@ -847,10 +869,11 @@ landing_getting_started_1: |- ], Some("reference_number")).await.unwrap(); tenant_token_guide_generate_sdk_1: |- let api_key = "B5KdX2MY2jV6EXfUs6scSfmC..."; + let api_key_uid = "6062abda-a5aa-4414-ac91-ecd7944c0f8d"; let expires_at = time::macros::datetime!(2025 - 12 - 20 00:00:00 UTC); let search_rules = json!({ "patient_medical_records": { "filter": "user_id = 1" } }); - let token = client.generate_tenant_token(search_rules, api_key, expires_at).unwrap(); + let token = client.generate_tenant_token(api_key_uid, search_rules, api_key, expires_at).unwrap(); tenant_token_guide_search_sdk_1: |- let front_end_client = Client::new("http://127.0.0.1:7700", token); let results: SearchResults = front_end_client.index("patient_medical_records") From 5943cb76d6670f98066bde9024bea1f122506440 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Tue, 30 Aug 2022 15:19:58 +0200 Subject: [PATCH 16/19] Update document query for v0.28.0 (#326) * Add a documentQuery structure to handler query parameters on get document * Fix clippy error * Improve comment test * Improve comment test --- src/documents.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++-- src/indexes.rs | 78 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 146 insertions(+), 18 deletions(-) diff --git a/src/documents.rs b/src/documents.rs index 5dfc2a3a..2048937e 100644 --- a/src/documents.rs +++ b/src/documents.rs @@ -11,11 +11,89 @@ pub struct DocumentsResults { #[derive(Debug, Clone, Serialize)] pub struct DocumentQuery<'a> { + #[serde(skip_serializing)] + pub index: &'a Index, + /// The fields that should appear in the documents. By default all of the fields are present. #[serde(skip_serializing_if = "Option::is_none")] pub fields: Option>, } +impl<'a> DocumentQuery<'a> { + pub fn new(index: &Index) -> DocumentQuery { + DocumentQuery { + index, + fields: None, + } + } + + /// Specify the fields to return in the document. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// let index = client.index("document_query_with_fields"); + /// let mut document_query = DocumentQuery::new(&index); + /// + /// document_query.with_fields(["title"]); + /// ``` + pub fn with_fields( + &mut self, + fields: impl IntoIterator, + ) -> &mut DocumentQuery<'a> { + self.fields = Some(fields.into_iter().collect()); + self + } + + /// Execute the get document query. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # use serde::{Deserialize, Serialize}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// + /// # futures::executor::block_on(async move { + /// #[derive(Debug, Serialize, Deserialize, PartialEq)] + /// struct MyObject { + /// id: String, + /// kind: String, + /// } + /// #[derive(Debug, Serialize, Deserialize, PartialEq)] + /// struct MyObjectReduced { + /// id: String, + /// } + /// + /// # let index = client.index("document_query_execute"); + /// # index.add_or_replace(&[MyObject{id:"1".to_string(), kind:String::from("a kind")},MyObject{id:"2".to_string(), kind:String::from("some kind")}], None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// + /// let document = DocumentQuery::new(&index).with_fields(["id"]).execute::("1").await.unwrap(); + /// + /// assert_eq!( + /// document, + /// MyObjectReduced { id: "1".to_string() } + /// ); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + pub async fn execute( + &self, + document_id: &str, + ) -> Result { + self.index.get_document_with::(document_id, self).await + } +} + #[derive(Debug, Clone, Serialize)] pub struct DocumentsQuery<'a> { #[serde(skip_serializing)] @@ -144,9 +222,13 @@ impl<'a> DocumentsQuery<'a> { /// } /// let index = client.index("documents_query_execute"); /// - /// let mut documents_query = DocumentsQuery::new(&index); + /// let document = DocumentsQuery::new(&index) + /// .with_offset(1) + /// .execute::() + /// .await + /// .unwrap(); /// - /// documents_query.with_offset(1).execute::().await.unwrap(); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` pub async fn execute( diff --git a/src/indexes.rs b/src/indexes.rs index 8570e3e0..afc99614 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -285,29 +285,21 @@ impl Index { /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); /// # - /// #[derive(Serialize, Debug)] + /// #[derive(Serialize, Deserialize, Debug, PartialEq)] /// struct Movie { /// name: String, - /// description: String, - /// age: Option - /// } - /// - /// #[derive(Deserialize, Debug, PartialEq)] - /// struct ReturnedMovie { - /// name: String, /// description: String /// } /// - /// /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// let movies = client.index("get_document"); - /// # 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."), age: Some(1)}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// /// // retrieve a document (you have to put the document in the index before) - /// let interstellar = movies.get_document::("Interstellar", Some(["name", "description"].to_vec())).await.unwrap(); + /// let interstellar = movies.get_document::("Interstellar").await.unwrap(); /// - /// assert_eq!(interstellar, ReturnedMovie { + /// assert_eq!(interstellar, 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."), /// }); @@ -317,16 +309,65 @@ impl Index { pub async fn get_document( &self, document_id: &str, - fields: Option>, ) -> Result { let url = format!( "{}/indexes/{}/documents/{}", self.client.host, self.uid, document_id ); - let query = DocumentQuery { fields }; + request::<(), T>(&url, &self.client.api_key, Method::Get(()), 200).await + } + + /// Get one document with parameters. + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, indexes::*, documents::*}; + /// # use serde::{Deserialize, Serialize}; + /// # + /// # let MEILISEARCH_HOST = option_env!("MEILISEARCH_HOST").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); + /// + /// # futures::executor::block_on(async move { + /// #[derive(Debug, Serialize, Deserialize, PartialEq)] + /// struct MyObject { + /// id: String, + /// kind: String, + /// } + /// #[derive(Debug, Serialize, Deserialize, PartialEq)] + /// struct MyObjectReduced { + /// id: String, + /// } + /// + /// # let index = client.index("document_query_execute"); + /// # index.add_or_replace(&[MyObject{id:"1".to_string(), kind:String::from("a kind")},MyObject{id:"2".to_string(), kind:String::from("some kind")}], None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// + /// let mut document_query = DocumentQuery::new(&index); + /// document_query.with_fields(["id"]); + /// + /// let document = index.get_document_with::("1", &document_query).await.unwrap(); + /// + /// assert_eq!( + /// document, + /// MyObjectReduced { id: "1".to_string() } + /// ); + /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); + /// # }); + pub async fn get_document_with( + &self, + document_id: &str, + document_query: &DocumentQuery<'_>, + ) -> Result { + let url = format!( + "{}/indexes/{}/documents/{}", + self.client.host, self.uid, document_id + ); - request::<&DocumentQuery, T>(&url, &self.client.api_key, Method::Get(&query), 200).await + request::<&DocumentQuery, T>(&url, &self.client.api_key, Method::Get(document_query), 200) + .await } /// Get [Document]s by batch. @@ -388,6 +429,10 @@ impl Index { /// description: String, /// } /// + /// #[derive(Deserialize, Debug, PartialEq)] + /// struct ReturnedMovie { + /// name: String, + /// } /// /// # futures::executor::block_on(async move { /// let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); @@ -397,8 +442,9 @@ impl Index { /// /// let mut query = DocumentsQuery::new(&movie_index); /// query.with_limit(1); + /// query.with_fields(["name"]); /// // retrieve movies (you have to put some movies in the index before) - /// let movies = movie_index.get_documents_with::(&query).await.unwrap(); + /// let movies = movie_index.get_documents_with::(&query).await.unwrap(); /// /// assert!(movies.results.len() == 1); /// # movie_index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); From e80878966b090901e4f1efac39710a398cce4970 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Tue, 30 Aug 2022 15:21:15 +0200 Subject: [PATCH 17/19] Fix wasm failing build due to getrandom in uuid library (#324) * Fix wasm failing build due to getrandom in uuid library * Add wasm compatibility with request --- Cargo.toml | 2 +- src/client.rs | 1 + src/errors.rs | 3 +++ src/request.rs | 27 ++++++++++++++++++--------- src/tenant_tokens.rs | 3 +++ 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9724a196..3180889a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,11 @@ serde_json = "1.0" time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing"] } jsonwebtoken = { version = "8", default-features = false } yaup = "0.2.0" -uuid = { version = "1.1.2", features = ["v4"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures = "0.3" isahc = { version = "1.0", features = ["http2", "text-decoding"], default_features = false } +uuid = { version = "1.1.2", features = ["v4"] } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.47" diff --git a/src/client.rs b/src/client.rs index 348b2217..e87e0df8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -810,6 +810,7 @@ impl Client { /// let client = client::Client::new(MEILISEARCH_HOST, token); /// # }); /// ``` + #[cfg(not(target_arch = "wasm32"))] pub fn generate_tenant_token( &self, api_key_uid: String, diff --git a/src/errors.rs b/src/errors.rs index b1daf14e..bbca6cec 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -37,6 +37,7 @@ pub enum Error { // The library formating the query parameters encountered an error. Yaup(yaup::Error), // The library validating the format of an uuid. + #[cfg(not(target_arch = "wasm32"))] Uuid(uuid::Error), // Error thrown in case the version of the Uuid is not v4. InvalidUuid4Version, @@ -79,6 +80,7 @@ impl From for Error { } } +#[cfg(not(target_arch = "wasm32"))] impl From for Error { fn from(error: uuid::Error) -> Error { Error::Uuid(error) @@ -205,6 +207,7 @@ impl std::fmt::Display for Error { Error::TenantTokensExpiredSignature => write!(fmt, "The provided expires_at is already expired."), Error::InvalidTenantToken(e) => write!(fmt, "Impossible to generate the token, jsonwebtoken encountered an error: {}", e), Error::Yaup(e) => write!(fmt, "Internal Error: could not parse the query parameters: {}", e), + #[cfg(not(target_arch = "wasm32"))] Error::Uuid(e) => write!(fmt, "The uid of the token has bit an uuid4 format: {}", e), Error::InvalidUuid4Version => write!(fmt, "The uid provided to the token is not of version uuidv4") } diff --git a/src/request.rs b/src/request.rs index e748d543..e3028a6b 100644 --- a/src/request.rs +++ b/src/request.rs @@ -112,7 +112,7 @@ pub(crate) async fn request { + Method::Get(query) => { + let query = yaup::to_string(query)?; + + mut_url = if query.is_empty() { + mut_url.to_string() + } else { + format!("{}?{}", mut_url, query) + }; + request.method("GET"); } Method::Delete => { @@ -145,13 +153,14 @@ pub(crate) async fn request Response::from(response), - Err(e) => { - error!("Network error: {:?}", e); - return Err(Error::UnreachableServer); - } - }; + let response = + match JsFuture::from(window.fetch_with_str_and_init(mut_url.as_str(), &request)).await { + Ok(response) => Response::from(response), + Err(e) => { + error!("Network error: {:?}", e); + return Err(Error::UnreachableServer); + } + }; let status = response.status() as u16; let text = match response.text() { Ok(text) => match JsFuture::from(text).await { diff --git a/src/tenant_tokens.rs b/src/tenant_tokens.rs index 83fc3704..fda30e02 100644 --- a/src/tenant_tokens.rs +++ b/src/tenant_tokens.rs @@ -3,9 +3,11 @@ use jsonwebtoken::{encode, EncodingKey, Header}; use serde::{Deserialize, Serialize}; use serde_json::Value; use time::OffsetDateTime; +#[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; #[derive(Debug, Serialize, Deserialize)] +#[cfg(not(target_arch = "wasm32"))] #[serde(rename_all = "camelCase")] struct TenantTokenClaim { api_key_uid: String, @@ -14,6 +16,7 @@ struct TenantTokenClaim { exp: Option, } +#[cfg(not(target_arch = "wasm32"))] pub fn generate_tenant_token( api_key_uid: String, search_rules: Value, From 563fa0830ff29af1032d85d40195fb47763f002f Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 30 Aug 2022 17:03:22 +0200 Subject: [PATCH 18/19] Improve documentation of get documents with offset --- src/documents.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/documents.rs b/src/documents.rs index 2048937e..5be9866b 100644 --- a/src/documents.rs +++ b/src/documents.rs @@ -144,9 +144,7 @@ impl<'a> DocumentsQuery<'a> { /// # let client = Client::new(MEILISEARCH_HOST, MEILISEARCH_API_KEY); /// let index = client.index("my_index"); /// - /// let mut documents_query = DocumentsQuery::new(&index); - /// - /// documents_query.with_offset(1); + /// let mut documents_query = DocumentsQuery::new(&index).with_offset(1); /// ``` pub fn with_offset(&mut self, offset: usize) -> &mut DocumentsQuery<'a> { self.offset = Some(offset); From d59b8e67986a55a63bf802885b0ad0e12c6bce7d Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Wed, 31 Aug 2022 15:56:06 +0200 Subject: [PATCH 19/19] Bump version to v0.18.0 (#328) * Update version for the next release (v0.18.0) * Re-generate the readme --- .code-samples.meilisearch.yaml | 2 +- Cargo.toml | 2 +- README.md | 2 +- README.tpl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index e1c6f345..5d571fa8 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -495,7 +495,7 @@ primary_field_guide_add_document_primary_key: |- getting_started_add_documents_md: |- ```toml [dependencies] - meilisearch-sdk = "0.17" + meilisearch-sdk = "0.18" # futures: because we want to block on futures futures = "0.3" # serde: required if you are going to use documents diff --git a/Cargo.toml b/Cargo.toml index 3180889a..179e3bc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "meilisearch-sdk" -version = "0.17.0" +version = "0.18.0" authors = ["Mubelotix "] edition = "2018" description = "Rust wrapper for the Meilisearch API. Meilisearch is a powerful, fast, open-source, easy to use and deploy search engine." diff --git a/README.md b/README.md index fcf6b2ca..5bea30c3 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ To use `meilisearch-sdk`, add this to your `Cargo.toml`: ```toml [dependencies] -meilisearch-sdk = "0.17.0" +meilisearch-sdk = "0.18.0" ``` The following optional dependencies may also be useful: diff --git a/README.tpl b/README.tpl index 2a519a08..213408fb 100644 --- a/README.tpl +++ b/README.tpl @@ -50,7 +50,7 @@ To use `meilisearch-sdk`, add this to your `Cargo.toml`: ```toml [dependencies] -meilisearch-sdk = "0.17.0" +meilisearch-sdk = "0.18.0" ``` The following optional dependencies may also be useful: