Skip to content

Commit

Permalink
Add filter parameter on get_documents for Meilisearch v1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
bidoubiwa committed May 25, 2023
1 parent 70043d5 commit 4a82f91
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 11 deletions.
126 changes: 124 additions & 2 deletions src/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ pub struct DocumentsQuery<'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<Vec<&'a str>>,

/// Filters to apply.
///
/// Available since v1.2 of Meilisearch
/// Read the [dedicated guide](https://docs.meilisearch.com/reference/features/filtering.html) to learn the syntax.
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<&'a str>,
}

impl<'a> DocumentsQuery<'a> {
Expand All @@ -194,6 +201,7 @@ impl<'a> DocumentsQuery<'a> {
offset: None,
limit: None,
fields: None,
filter: None,
}
}

Expand Down Expand Up @@ -264,6 +272,11 @@ impl<'a> DocumentsQuery<'a> {
self
}

pub fn with_filter<'b>(&'b mut self, filter: &'a str) -> &'b mut DocumentsQuery<'a> {
self.filter = Some(filter);
self
}

/// Execute the get documents query.
///
/// # Example
Expand Down Expand Up @@ -304,8 +317,7 @@ impl<'a> DocumentsQuery<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{client::*, indexes::*};
use ::meilisearch_sdk::documents::IndexConfig;
use crate::{client::*, errors::*, indexes::*};
use meilisearch_test_macro::meilisearch_test;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -407,6 +419,116 @@ mod tests {
Ok(())
}

#[meilisearch_test]
async fn test_get_documents_with_filter(client: Client, index: Index) -> Result<(), Error> {
setup_test_index(&client, &index).await?;

index
.set_filterable_attributes(["id"])
.await
.unwrap()
.wait_for_completion(&client, None, None)
.await
.unwrap();

let documents = DocumentsQuery::new(&index)
.with_filter("id = 1")
.execute::<MyObject>()
.await?;

assert_eq!(documents.results.len(), 1);

Ok(())
}

#[meilisearch_test]
async fn test_get_documents_with_error_hint() -> Result<(), Error> {
let url = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
let client = Client::new(format!("{}/hello", url), Some("masterKey"));
let index = client.index("test_get_documents_with_filter_wrong_ms_version");

let documents = DocumentsQuery::new(&index)
.with_filter("id = 1")
.execute::<MyObject>()
.await;

let error = documents.unwrap_err();

let message = Some("Hint: It might not be working because you're not up to date with the Meilisearch version that updated the get_documents_with method.".to_string());
let url = "http://localhost:7700/hello/indexes/test_get_documents_with_filter_wrong_ms_version/documents/fetch".to_string();
let status_code = 404;
let displayed_error = "MeilisearchCommunicationError: The server responded with a 404. Hint: It might not be working because you're not up to date with the Meilisearch version that updated the get_documents_with method.\nurl: http://localhost:7700/hello/indexes/test_get_documents_with_filter_wrong_ms_version/documents/fetch";

match &error {
Error::MeilisearchCommunication(error) => {
assert_eq!(error.status_code, status_code);
assert_eq!(error.message, message);
assert_eq!(error.url, url);
}
_ => panic!("The error was expected to be a MeilisearchCommunicationError error, but it was not."),
};
assert_eq!(format!("{}", error), displayed_error);

Ok(())
}

#[meilisearch_test]
async fn test_get_documents_with_error_hint_meilisearch_api_error(
index: Index,
client: Client,
) -> Result<(), Error> {
setup_test_index(&client, &index).await?;

let error = DocumentsQuery::new(&index)
.with_filter("id = 1")
.execute::<MyObject>()
.await
.unwrap_err();

let message = "Attribute `id` is not filterable. This index does not have configured filterable attributes.
1:3 id = 1
Hint: It might not be working because you're not up to date with the Meilisearch version that updated the get_documents_with method.".to_string();
let displayed_error = "Meilisearch invalid_request: invalid_document_filter: Attribute `id` is not filterable. This index does not have configured filterable attributes.
1:3 id = 1
Hint: It might not be working because you're not up to date with the Meilisearch version that updated the get_documents_with method.. https://docs.meilisearch.com/errors#invalid_document_filter";

match &error {
Error::Meilisearch(error) => {
assert_eq!(error.error_message, message);
}
_ => panic!("The error was expected to be a MeilisearchCommunicationError error, but it was not."),
};
assert_eq!(format!("{}", error), displayed_error);

Ok(())
}

#[meilisearch_test]
async fn test_get_documents_with_invalid_filter(
client: Client,
index: Index,
) -> Result<(), Error> {
setup_test_index(&client, &index).await?;

// Does not work because `id` is not filterable
let error = DocumentsQuery::new(&index)
.with_filter("id = 1")
.execute::<MyObject>()
.await
.unwrap_err();

assert!(matches!(
error,
Error::Meilisearch(MeilisearchError {
error_code: ErrorCode::InvalidDocumentFilter,
error_type: ErrorType::InvalidRequest,
..
})
));

Ok(())
}

#[meilisearch_test]
async fn test_settings_generated_by_macro(client: Client, index: Index) -> Result<(), Error> {
setup_test_index(&client, &index).await?;
Expand Down
52 changes: 52 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub enum Error {
/// Also check out: <https://github.com/meilisearch/Meilisearch/blob/main/meilisearch-error/src/lib.rs>
#[error(transparent)]
Meilisearch(#[from] MeilisearchError),
#[error(transparent)]
MeilisearchCommunication(#[from] MeilisearchCommunicationError),
/// There is no Meilisearch server listening on the [specified host]
/// (../client/struct.Client.html#method.new).
#[error("The Meilisearch server can't be reached.")]
Expand Down Expand Up @@ -65,6 +67,30 @@ pub enum Error {
InvalidUuid4Version,
}

#[derive(Debug, Clone, Deserialize, Error)]
#[serde(rename_all = "camelCase")]

pub struct MeilisearchCommunicationError {
pub status_code: u16,
pub message: Option<String>,
pub url: String,
}

impl std::fmt::Display for MeilisearchCommunicationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MeilisearchCommunicationError: The server responded with a {}.",
self.status_code
)?;
if let Some(message) = &self.message {
write!(f, " {}", message)?;
}
write!(f, "\nurl: {}", self.url)?;
Ok(())
}
}

#[derive(Debug, Clone, Deserialize, Error)]
#[serde(rename_all = "camelCase")]
#[error("Meilisearch {}: {}: {}. {}", .error_type, .error_code, .error_message, .error_link)]
Expand Down Expand Up @@ -162,6 +188,8 @@ pub enum ErrorCode {
InvalidIndexOffset,
InvalidIndexLimit,
InvalidIndexPrimaryKey,
InvalidDocumentFilter,
MissingDocumentFilter,
InvalidDocumentFields,
InvalidDocumentLimit,
InvalidDocumentOffset,
Expand Down Expand Up @@ -234,6 +262,8 @@ pub enum ErrorCode {
Unknown,
}

pub const MEILISEARCH_VERSION_HINT: &str = "Hint: It might not be working because you're not up to date with the Meilisearch version that updated the get_documents_with method.";

impl std::fmt::Display for ErrorCode {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(
Expand Down Expand Up @@ -311,6 +341,28 @@ mod test {

assert_eq!(error.to_string(), ("Meilisearch internal: index_creation_failed: The cool error message.. https://the best link eveer"));

let error: MeilisearchCommunicationError = MeilisearchCommunicationError {
status_code: 404,
message: Some("Hint: something.".to_string()),
url: "http://localhost:7700/something".to_string(),
};

assert_eq!(
error.to_string(),
("MeilisearchCommunicationError: The server responded with a 404. Hint: something.\nurl: http://localhost:7700/something")
);

let error: MeilisearchCommunicationError = MeilisearchCommunicationError {
status_code: 404,
message: None,
url: "http://localhost:7700/something".to_string(),
};

assert_eq!(
error.to_string(),
("MeilisearchCommunicationError: The server responded with a 404.\nurl: http://localhost:7700/something")
);

let error = Error::UnreachableServer;
assert_eq!(
error.to_string(),
Expand Down
32 changes: 31 additions & 1 deletion src/indexes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
client::Client,
documents::{DocumentQuery, DocumentsQuery, DocumentsResults},
errors::Error,
errors::{Error, MeilisearchCommunicationError, MeilisearchError, MEILISEARCH_VERSION_HINT},
request::*,
search::*,
task_info::TaskInfo,
Expand Down Expand Up @@ -466,6 +466,36 @@ impl Index {
&self,
documents_query: &DocumentsQuery<'_>,
) -> Result<DocumentsResults<T>, Error> {
if documents_query.filter.is_some() {
let url = format!("{}/indexes/{}/documents/fetch", self.client.host, self.uid);
return request::<(), &DocumentsQuery, DocumentsResults<T>>(
&url,
self.client.get_api_key(),
Method::Post {
body: documents_query,
query: (),
},
200,
)
.await
.map_err(|err| match err {
Error::MeilisearchCommunication(error) => {
Error::MeilisearchCommunication(MeilisearchCommunicationError {
status_code: error.status_code,
url: error.url,
message: Some(format!("{}", MEILISEARCH_VERSION_HINT)),
})
}
Error::Meilisearch(error) => Error::Meilisearch(MeilisearchError {
error_code: error.error_code,
error_link: error.error_link,
error_type: error.error_type,
error_message: format!("{}\n{}", error.error_message, MEILISEARCH_VERSION_HINT),
}),
_ => err,
});
}

let url = format!("{}/indexes/{}/documents", self.client.host, self.uid);
request::<&DocumentsQuery, (), DocumentsResults<T>>(
&url,
Expand Down
32 changes: 24 additions & 8 deletions src/request.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::errors::{Error, MeilisearchError};
use crate::errors::{Error, MeilisearchCommunicationError, MeilisearchError};
use log::{error, trace, warn};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::{from_str, to_string};
Expand Down Expand Up @@ -116,7 +116,7 @@ pub(crate) async fn request<
body = "null".to_string();
}

parse_response(status, expected_status_code, body)
parse_response(status, expected_status_code, body, url.to_string())
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -214,7 +214,7 @@ pub(crate) async fn stream_request<
body = "null".to_string();
}

parse_response(status, expected_status_code, body)
parse_response(status, expected_status_code, body, url.to_string())
}

#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -318,9 +318,14 @@ pub(crate) async fn request<

if let Some(t) = text.as_string() {
if t.is_empty() {
parse_response(status, expected_status_code, String::from("null"))
parse_response(
status,
expected_status_code,
String::from("null"),
url.to_string(),
)
} else {
parse_response(status, expected_status_code, t)
parse_response(status, expected_status_code, t, url.to_string())
}
} else {
error!("Invalid response");
Expand All @@ -332,6 +337,7 @@ fn parse_response<Output: DeserializeOwned>(
status_code: u16,
expected_status_code: u16,
body: String,
url: String,
) -> Result<Output, Error> {
if status_code == expected_status_code {
match from_str::<Output>(&body) {
Expand All @@ -345,16 +351,26 @@ fn parse_response<Output: DeserializeOwned>(
}
};
}
// TODO: create issue where it is clear what the HTTP error is
// ParseError(Error("invalid type: null, expected struct MeilisearchError", line: 1, column: 4))

warn!(
"Expected response code {}, got {}",
expected_status_code, status_code
);

match from_str::<MeilisearchError>(&body) {
Ok(e) => Err(Error::from(e)),
Err(e) => Err(Error::ParseError(e)),
Err(e) => {
if status_code >= 400 {
return Err(Error::MeilisearchCommunication(
MeilisearchCommunicationError {
status_code,
message: None,
url,
},
));
}
Err(Error::ParseError(e))
}
}
}

Expand Down

0 comments on commit 4a82f91

Please sign in to comment.