Skip to content

Add get documents filter field for ms v1.2 #473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
35 changes: 34 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,39 @@ 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