Skip to content

Commit 0b5d7e9

Browse files
committed
Merge branch 'bump-meilisearch-v1.2.0' into delete-documents-by-filter-ms-v1.2
2 parents 977e9da + cd3021c commit 0b5d7e9

File tree

4 files changed

+231
-9
lines changed

4 files changed

+231
-9
lines changed

src/documents.rs

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

191198
impl<'a> DocumentsQuery<'a> {
@@ -195,6 +202,7 @@ impl<'a> DocumentsQuery<'a> {
195202
offset: None,
196203
limit: None,
197204
fields: None,
205+
filter: None,
198206
}
199207
}
200208

@@ -265,6 +273,11 @@ impl<'a> DocumentsQuery<'a> {
265273
self
266274
}
267275

276+
pub fn with_filter<'b>(&'b mut self, filter: &'a str) -> &'b mut DocumentsQuery<'a> {
277+
self.filter = Some(filter);
278+
self
279+
}
280+
268281
/// Execute the get documents query.
269282
///
270283
/// # Example
@@ -495,6 +508,116 @@ mod tests {
495508
Ok(())
496509
}
497510

511+
#[meilisearch_test]
512+
async fn test_get_documents_with_filter(client: Client, index: Index) -> Result<(), Error> {
513+
setup_test_index(&client, &index).await?;
514+
515+
index
516+
.set_filterable_attributes(["id"])
517+
.await
518+
.unwrap()
519+
.wait_for_completion(&client, None, None)
520+
.await
521+
.unwrap();
522+
523+
let documents = DocumentsQuery::new(&index)
524+
.with_filter("id = 1")
525+
.execute::<MyObject>()
526+
.await?;
527+
528+
assert_eq!(documents.results.len(), 1);
529+
530+
Ok(())
531+
}
532+
533+
#[meilisearch_test]
534+
async fn test_get_documents_with_error_hint() -> Result<(), Error> {
535+
let url = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
536+
let client = Client::new(format!("{}/hello", url), Some("masterKey"));
537+
let index = client.index("test_get_documents_with_filter_wrong_ms_version");
538+
539+
let documents = DocumentsQuery::new(&index)
540+
.with_filter("id = 1")
541+
.execute::<MyObject>()
542+
.await;
543+
544+
let error = documents.unwrap_err();
545+
546+
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());
547+
let url = "http://localhost:7700/hello/indexes/test_get_documents_with_filter_wrong_ms_version/documents/fetch".to_string();
548+
let status_code = 404;
549+
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";
550+
551+
match &error {
552+
Error::MeilisearchCommunication(error) => {
553+
assert_eq!(error.status_code, status_code);
554+
assert_eq!(error.message, message);
555+
assert_eq!(error.url, url);
556+
}
557+
_ => panic!("The error was expected to be a MeilisearchCommunicationError error, but it was not."),
558+
};
559+
assert_eq!(format!("{}", error), displayed_error);
560+
561+
Ok(())
562+
}
563+
564+
#[meilisearch_test]
565+
async fn test_get_documents_with_error_hint_meilisearch_api_error(
566+
index: Index,
567+
client: Client,
568+
) -> Result<(), Error> {
569+
setup_test_index(&client, &index).await?;
570+
571+
let error = DocumentsQuery::new(&index)
572+
.with_filter("id = 1")
573+
.execute::<MyObject>()
574+
.await
575+
.unwrap_err();
576+
577+
let message = "Attribute `id` is not filterable. This index does not have configured filterable attributes.
578+
1:3 id = 1
579+
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();
580+
let displayed_error = "Meilisearch invalid_request: invalid_document_filter: Attribute `id` is not filterable. This index does not have configured filterable attributes.
581+
1:3 id = 1
582+
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";
583+
584+
match &error {
585+
Error::Meilisearch(error) => {
586+
assert_eq!(error.error_message, message);
587+
}
588+
_ => panic!("The error was expected to be a MeilisearchCommunicationError error, but it was not."),
589+
};
590+
assert_eq!(format!("{}", error), displayed_error);
591+
592+
Ok(())
593+
}
594+
595+
#[meilisearch_test]
596+
async fn test_get_documents_with_invalid_filter(
597+
client: Client,
598+
index: Index,
599+
) -> Result<(), Error> {
600+
setup_test_index(&client, &index).await?;
601+
602+
// Does not work because `id` is not filterable
603+
let error = DocumentsQuery::new(&index)
604+
.with_filter("id = 1")
605+
.execute::<MyObject>()
606+
.await
607+
.unwrap_err();
608+
609+
assert!(matches!(
610+
error,
611+
Error::Meilisearch(MeilisearchError {
612+
error_code: ErrorCode::InvalidDocumentFilter,
613+
error_type: ErrorType::InvalidRequest,
614+
..
615+
})
616+
));
617+
618+
Ok(())
619+
}
620+
498621
#[meilisearch_test]
499622
async fn test_settings_generated_by_macro(client: Client, index: Index) -> Result<(), Error> {
500623
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)]
@@ -236,6 +262,8 @@ pub enum ErrorCode {
236262
Unknown,
237263
}
238264

265+
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";
266+
239267
impl std::fmt::Display for ErrorCode {
240268
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
241269
write!(
@@ -313,6 +341,28 @@ mod test {
313341

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

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

src/indexes.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
client::Client,
33
documents::{DocumentDeletionQuery, DocumentQuery, DocumentsQuery, DocumentsResults},
4-
errors::Error,
4+
errors::{Error, MeilisearchCommunicationError, MeilisearchError, MEILISEARCH_VERSION_HINT},
55
request::*,
66
search::*,
77
task_info::TaskInfo,
@@ -466,6 +466,39 @@ 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(format!("{}.", MEILISEARCH_VERSION_HINT)),
487+
})
488+
}
489+
Error::Meilisearch(error) => Error::Meilisearch(MeilisearchError {
490+
error_code: error.error_code,
491+
error_link: error.error_link,
492+
error_type: error.error_type,
493+
error_message: format!(
494+
"{}\n{}.",
495+
error.error_message, MEILISEARCH_VERSION_HINT
496+
),
497+
}),
498+
_ => err,
499+
});
500+
}
501+
469502
let url = format!("{}/indexes/{}/documents", self.client.host, self.uid);
470503
request::<&DocumentsQuery, (), DocumentsResults<T>>(
471504
&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)