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); + } +}