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.