Skip to content

Commit fc5be60

Browse files
committed
Add filter parameter on get_documents for Meilisearch v1.2
1 parent 70043d5 commit fc5be60

File tree

4 files changed

+166
-11
lines changed

4 files changed

+166
-11
lines changed

src/documents.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ pub struct DocumentsQuery<'a> {
185185
/// The fields that should appear in the documents. By default all of the fields are present.
186186
#[serde(skip_serializing_if = "Option::is_none")]
187187
pub fields: Option<Vec<&'a str>>,
188+
189+
/// Filters to apply.
190+
///
191+
/// Available since v1.2 of Meilisearch
192+
/// Read the [dedicated guide](https://docs.meilisearch.com/reference/features/filtering.html) to learn the syntax.
193+
#[serde(skip_serializing_if = "Option::is_none")]
194+
pub filter: Option<&'a str>,
188195
}
189196

190197
impl<'a> DocumentsQuery<'a> {
@@ -194,6 +201,7 @@ impl<'a> DocumentsQuery<'a> {
194201
offset: None,
195202
limit: None,
196203
fields: None,
204+
filter: None,
197205
}
198206
}
199207

@@ -264,6 +272,11 @@ impl<'a> DocumentsQuery<'a> {
264272
self
265273
}
266274

275+
pub fn with_filter<'b>(&'b mut self, filter: &'a str) -> &'b mut DocumentsQuery<'a> {
276+
self.filter = Some(filter);
277+
self
278+
}
279+
267280
/// Execute the get documents query.
268281
///
269282
/// # Example
@@ -304,8 +317,7 @@ impl<'a> DocumentsQuery<'a> {
304317
#[cfg(test)]
305318
mod tests {
306319
use super::*;
307-
use crate::{client::*, indexes::*};
308-
use ::meilisearch_sdk::documents::IndexConfig;
320+
use crate::{client::*, errors::*, indexes::*};
309321
use meilisearch_test_macro::meilisearch_test;
310322
use serde::{Deserialize, Serialize};
311323

@@ -407,6 +419,59 @@ mod tests {
407419
Ok(())
408420
}
409421

422+
#[meilisearch_test]
423+
async fn test_get_documents_with_filter(client: Client, index: Index) -> Result<(), Error> {
424+
setup_test_index(&client, &index).await?;
425+
426+
index
427+
.set_filterable_attributes(["id"])
428+
.await
429+
.unwrap()
430+
.wait_for_completion(&client, None, None)
431+
.await
432+
.unwrap();
433+
434+
let documents = DocumentsQuery::new(&index)
435+
.with_filter("id = 1")
436+
.execute::<MyObject>()
437+
.await?;
438+
439+
assert_eq!(documents.results.len(), 1);
440+
441+
Ok(())
442+
}
443+
444+
#[meilisearch_test]
445+
async fn test_get_documents_with_error_hint() -> Result<(), Error> {
446+
let url = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
447+
let client = Client::new(format!("{}/hello", url), Some("masterKey"));
448+
let index = client.index("test_get_documents_with_filter_wrong_ms_version");
449+
450+
let documents = DocumentsQuery::new(&index)
451+
.with_filter("id = 1")
452+
.execute::<MyObject>()
453+
.await;
454+
455+
let error = documents.unwrap_err();
456+
457+
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());
458+
let url = "http://localhost:7700/hello/indexes/test_get_documents_with_filter_wrong_ms_version/documents/fetch".to_string();
459+
let status_code = 404;
460+
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";
461+
462+
match &error {
463+
Error::MeilisearchCommunication(error) => {
464+
assert_eq!(error.status_code, status_code);
465+
assert_eq!(error.message, message);
466+
assert_eq!(error.url, url);
467+
}
468+
_ => panic!("The error was expected to be a MeilisearchCommunicationError error, but it was not."),
469+
};
470+
assert_eq!(format!("{}", error), displayed_error);
471+
472+
Ok(())
473+
}
474+
410475
#[meilisearch_test]
411476
async fn test_settings_generated_by_macro(client: Client, index: Index) -> Result<(), Error> {
412477
setup_test_index(&client, &index).await?;

src/errors.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ pub enum Error {
1111
/// Also check out: <https://github.com/meilisearch/Meilisearch/blob/main/meilisearch-error/src/lib.rs>
1212
#[error(transparent)]
1313
Meilisearch(#[from] MeilisearchError),
14+
#[error(transparent)]
15+
MeilisearchCommunication(#[from] MeilisearchCommunicationError),
1416
/// There is no Meilisearch server listening on the [specified host]
1517
/// (../client/struct.Client.html#method.new).
1618
#[error("The Meilisearch server can't be reached.")]
@@ -65,6 +67,30 @@ pub enum Error {
6567
InvalidUuid4Version,
6668
}
6769

70+
#[derive(Debug, Clone, Deserialize, Error)]
71+
#[serde(rename_all = "camelCase")]
72+
73+
pub struct MeilisearchCommunicationError {
74+
pub status_code: u16,
75+
pub message: Option<String>,
76+
pub url: String,
77+
}
78+
79+
impl std::fmt::Display for MeilisearchCommunicationError {
80+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81+
write!(
82+
f,
83+
"MeilisearchCommunicationError: The server responded with a {}.",
84+
self.status_code
85+
)?;
86+
if let Some(message) = &self.message {
87+
write!(f, " {}", message)?;
88+
}
89+
write!(f, "\nurl: {}", self.url)?;
90+
Ok(())
91+
}
92+
}
93+
6894
#[derive(Debug, Clone, Deserialize, Error)]
6995
#[serde(rename_all = "camelCase")]
7096
#[error("Meilisearch {}: {}: {}. {}", .error_type, .error_code, .error_message, .error_link)]
@@ -162,6 +188,8 @@ pub enum ErrorCode {
162188
InvalidIndexOffset,
163189
InvalidIndexLimit,
164190
InvalidIndexPrimaryKey,
191+
InvalidDocumentFilter,
192+
MissingDocumentFilter,
165193
InvalidDocumentFields,
166194
InvalidDocumentLimit,
167195
InvalidDocumentOffset,
@@ -311,6 +339,28 @@ mod test {
311339

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

342+
let error: MeilisearchCommunicationError = MeilisearchCommunicationError {
343+
status_code: 404,
344+
message: Some("Hint: something.".to_string()),
345+
url: "http://localhost:7700/something".to_string(),
346+
};
347+
348+
assert_eq!(
349+
error.to_string(),
350+
("MeilisearchCommunicationError: The server responded with a 404. Hint: something.\nurl: http://localhost:7700/something")
351+
);
352+
353+
let error: MeilisearchCommunicationError = MeilisearchCommunicationError {
354+
status_code: 404,
355+
message: None,
356+
url: "http://localhost:7700/something".to_string(),
357+
};
358+
359+
assert_eq!(
360+
error.to_string(),
361+
("MeilisearchCommunicationError: The server responded with a 404.\nurl: http://localhost:7700/something")
362+
);
363+
314364
let error = Error::UnreachableServer;
315365
assert_eq!(
316366
error.to_string(),

src/indexes.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
client::Client,
33
documents::{DocumentQuery, DocumentsQuery, DocumentsResults},
4-
errors::Error,
4+
errors::{Error, MeilisearchCommunicationError},
55
request::*,
66
search::*,
77
task_info::TaskInfo,
@@ -466,6 +466,30 @@ impl Index {
466466
&self,
467467
documents_query: &DocumentsQuery<'_>,
468468
) -> Result<DocumentsResults<T>, Error> {
469+
if documents_query.filter.is_some() {
470+
let url = format!("{}/indexes/{}/documents/fetch", self.client.host, self.uid);
471+
return request::<(), &DocumentsQuery, DocumentsResults<T>>(
472+
&url,
473+
self.client.get_api_key(),
474+
Method::Post {
475+
body: documents_query,
476+
query: (),
477+
},
478+
200,
479+
)
480+
.await
481+
.map_err(|err| match err {
482+
Error::MeilisearchCommunication(error) => {
483+
Error::MeilisearchCommunication(MeilisearchCommunicationError {
484+
status_code: error.status_code,
485+
url: error.url,
486+
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()),
487+
})
488+
}
489+
_ => err,
490+
});
491+
}
492+
469493
let url = format!("{}/indexes/{}/documents", self.client.host, self.uid);
470494
request::<&DocumentsQuery, (), DocumentsResults<T>>(
471495
&url,

src/request.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::errors::{Error, MeilisearchError};
1+
use crate::errors::{Error, MeilisearchCommunicationError, MeilisearchError};
22
use log::{error, trace, warn};
33
use serde::{de::DeserializeOwned, Serialize};
44
use serde_json::{from_str, to_string};
@@ -116,7 +116,7 @@ pub(crate) async fn request<
116116
body = "null".to_string();
117117
}
118118

119-
parse_response(status, expected_status_code, body)
119+
parse_response(status, expected_status_code, body, url.to_string())
120120
}
121121

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

217-
parse_response(status, expected_status_code, body)
217+
parse_response(status, expected_status_code, body, url.to_string())
218218
}
219219

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

319319
if let Some(t) = text.as_string() {
320320
if t.is_empty() {
321-
parse_response(status, expected_status_code, String::from("null"))
321+
parse_response(
322+
status,
323+
expected_status_code,
324+
String::from("null"),
325+
url.to_string(),
326+
)
322327
} else {
323-
parse_response(status, expected_status_code, t)
328+
parse_response(status, expected_status_code, t, url.to_string())
324329
}
325330
} else {
326331
error!("Invalid response");
@@ -332,6 +337,7 @@ fn parse_response<Output: DeserializeOwned>(
332337
status_code: u16,
333338
expected_status_code: u16,
334339
body: String,
340+
url: String,
335341
) -> Result<Output, Error> {
336342
if status_code == expected_status_code {
337343
match from_str::<Output>(&body) {
@@ -345,16 +351,26 @@ fn parse_response<Output: DeserializeOwned>(
345351
}
346352
};
347353
}
348-
// TODO: create issue where it is clear what the HTTP error is
349-
// ParseError(Error("invalid type: null, expected struct MeilisearchError", line: 1, column: 4))
350354

351355
warn!(
352356
"Expected response code {}, got {}",
353357
expected_status_code, status_code
354358
);
359+
355360
match from_str::<MeilisearchError>(&body) {
356361
Ok(e) => Err(Error::from(e)),
357-
Err(e) => Err(Error::ParseError(e)),
362+
Err(e) => {
363+
if status_code >= 400 {
364+
return Err(Error::MeilisearchCommunication(
365+
MeilisearchCommunicationError {
366+
status_code,
367+
message: None,
368+
url,
369+
},
370+
));
371+
}
372+
Err(Error::ParseError(e))
373+
}
358374
}
359375
}
360376

0 commit comments

Comments
 (0)