diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 29cafe68d..3cd3c3f60 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -1,9 +1,7 @@ -use std::collections::{HashSet, HashMap}; +use std::collections::{HashMap, HashSet}; +use actix_web::{get, post, web, HttpResponse}; use log::warn; -use actix_web::web; -use actix_web::HttpResponse; -use actix_web::{get, post}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -14,11 +12,10 @@ use crate::routes::IndexParam; use crate::Data; use meilisearch_core::facets::FacetFilter; -use meilisearch_schema::{Schema, FieldId}; +use meilisearch_schema::{FieldId, Schema}; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(search_with_post) - .service(search_with_url_query); + cfg.service(search_with_post).service(search_with_url_query); } #[derive(Serialize, Deserialize)] @@ -93,7 +90,11 @@ async fn search_with_post( } impl SearchQuery { - fn search(&self, index_uid: &str, data: web::Data) -> Result { + fn search( + &self, + index_uid: &str, + data: web::Data, + ) -> Result { let index = data .db .open_index(index_uid) @@ -111,6 +112,7 @@ impl SearchQuery { .and_then(|q| if q.is_empty() { None } else { Some(q) }); let mut search_builder = index.new_search(query); + if let Some(offset) = self.offset { search_builder.offset(offset); } @@ -122,7 +124,8 @@ impl SearchQuery { let mut restricted_attributes: HashSet<&str>; match &self.attributes_to_retrieve { Some(attributes_to_retrieve) => { - let attributes_to_retrieve: HashSet<&str> = attributes_to_retrieve.split(',').collect(); + let attributes_to_retrieve: HashSet<&str> = + attributes_to_retrieve.split(',').collect(); if attributes_to_retrieve.contains("*") { restricted_attributes = available_attributes.clone(); } else { @@ -136,15 +139,22 @@ impl SearchQuery { } } } - }, + } None => { restricted_attributes = available_attributes.clone(); } } if let Some(ref facet_filters) = self.facet_filters { - let attrs = index.main.attributes_for_faceting(&reader)?.unwrap_or_default(); - search_builder.add_facet_filters(FacetFilter::from_str(facet_filters, &schema, &attrs)?); + let attrs = index + .main + .attributes_for_faceting(&reader)? + .unwrap_or_default(); + search_builder.add_facet_filters(FacetFilter::from_str( + facet_filters, + &schema, + &attrs, + )?); } if let Some(facets) = &self.facets_distribution { @@ -152,7 +162,7 @@ impl SearchQuery { Some(ref attrs) => { let field_ids = prepare_facet_list(&facets, &schema, attrs)?; search_builder.add_facets(field_ids); - }, + } None => return Err(FacetCountError::NoFacetSet.into()), } } @@ -164,20 +174,23 @@ impl SearchQuery { for attribute in attributes_to_crop.split(',') { let mut attribute = attribute.split(':'); let attr = attribute.next(); - let length = attribute.next().and_then(|s| s.parse().ok()).unwrap_or(default_length); + let length = attribute + .next() + .and_then(|s| s.parse().ok()) + .unwrap_or(default_length); match attr { Some("*") => { for attr in &restricted_attributes { final_attributes.insert(attr.to_string(), length); } - }, + } Some(attr) => { if available_attributes.contains(attr) { final_attributes.insert(attr.to_string(), length); } else { warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); } - }, + } None => (), } } @@ -219,7 +232,11 @@ impl SearchQuery { /// /// An error is returned if the array is malformed, or if it contains attributes that are /// unexisting, or not set as facets. -fn prepare_facet_list(facets: &str, schema: &Schema, facet_attrs: &[FieldId]) -> Result, FacetCountError> { +fn prepare_facet_list( + facets: &str, + schema: &Schema, + facet_attrs: &[FieldId], +) -> Result, FacetCountError> { let json_array = serde_json::from_str(facets)?; match json_array { Value::Array(vals) => { @@ -247,6 +264,6 @@ fn prepare_facet_list(facets: &str, schema: &Schema, facet_attrs: &[FieldId]) -> } Ok(field_ids) } - bad_val => Err(FacetCountError::unexpected_token(bad_val, &["[String]"])) + bad_val => Err(FacetCountError::unexpected_token(bad_val, &["[String]"])), } } diff --git a/meilisearch-http/tests/placeholder_search.rs b/meilisearch-http/tests/placeholder_search.rs index 4d380563f..dde85b470 100644 --- a/meilisearch-http/tests/placeholder_search.rs +++ b/meilisearch-http/tests/placeholder_search.rs @@ -2,10 +2,11 @@ use std::convert::Into; use serde_json::json; use serde_json::Value; -use std::sync::Mutex; use std::cell::RefCell; +use std::sync::Mutex; -#[macro_use] mod common; +#[macro_use] +mod common; #[actix_rt::test] async fn placeholder_search_with_limit() { @@ -36,7 +37,12 @@ async fn placeholder_search_with_offset() { assert_eq!(status_code, 200); // take results at offset 3 as reference let lock = expected.lock().unwrap(); - lock.replace(response["hits"].as_array().unwrap()[3..6].iter().cloned().collect()); + lock.replace( + response["hits"].as_array().unwrap()[3..6] + .iter() + .cloned() + .collect(), + ); }); let expected = expected.into_inner().unwrap().into_inner(); @@ -64,11 +70,7 @@ async fn placeholder_search_with_attribute_to_highlight_wildcard() { test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 200); - let result = response["hits"] - .as_array() - .unwrap()[0] - .as_object() - .unwrap(); + let result = response["hits"].as_array().unwrap()[0].as_object().unwrap(); for value in result.values() { assert!(value.to_string().find("").is_none()); } @@ -135,11 +137,7 @@ async fn placeholder_search_with_attributes_to_retrieve() { }); test_post_get_search!(server, query, |response, _status_code| { - let hit = response["hits"] - .as_array() - .unwrap()[0] - .as_object() - .unwrap(); + let hit = response["hits"].as_array().unwrap()[0].as_object().unwrap(); assert_eq!(hit.values().count(), 2); let _ = hit["gender"]; let _ = hit["about"]; @@ -166,7 +164,9 @@ async fn placeholder_search_with_filter() { test_post_get_search!(server, query, |response, _status_code| { let hits = response["hits"].as_array().unwrap(); let value = Value::String(String::from("bug")); - assert!(hits.iter().all(|v| v["tags"].as_array().unwrap().contains(&value))); + assert!(hits + .iter() + .all(|v| v["tags"].as_array().unwrap().contains(&value))); }); let query = json!({ @@ -176,10 +176,9 @@ async fn placeholder_search_with_filter() { let hits = response["hits"].as_array().unwrap(); let bug = Value::String(String::from("bug")); let wontfix = Value::String(String::from("wontfix")); - assert!(hits.iter().all(|v| - v["color"].as_str().unwrap() == "Green" && - v["tags"].as_array().unwrap().contains(&bug) || - v["tags"].as_array().unwrap().contains(&wontfix))); + assert!(hits.iter().all(|v| v["color"].as_str().unwrap() == "Green" + && v["tags"].as_array().unwrap().contains(&bug) + || v["tags"].as_array().unwrap().contains(&wontfix))); }); } @@ -257,7 +256,12 @@ async fn placeholder_test_faceted_search_valid() { .as_array() .unwrap() .iter() - .all(|value| value.get("tags").unwrap().as_array().unwrap().contains(&Value::String("bug".to_owned())))); + .all(|value| value + .get("tags") + .unwrap() + .as_array() + .unwrap() + .contains(&Value::String("bug".to_owned())))); }); // test and: ["color:blue", "tags:bug"] @@ -272,10 +276,13 @@ async fn placeholder_test_faceted_search_valid() { .as_array() .unwrap() .iter() - .all(|value| value - .get("color") - .unwrap() == "blue" - && value.get("tags").unwrap().as_array().unwrap().contains(&Value::String("bug".to_owned())))); + .all(|value| value.get("color").unwrap() == "blue" + && value + .get("tags") + .unwrap() + .as_array() + .unwrap() + .contains(&Value::String("bug".to_owned())))); }); // test or: [["color:blue", "color:green"]] @@ -290,13 +297,8 @@ async fn placeholder_test_faceted_search_valid() { .as_array() .unwrap() .iter() - .all(|value| - value - .get("color") - .unwrap() == "blue" - || value - .get("color") - .unwrap() == "Green")); + .all(|value| value.get("color").unwrap() == "blue" + || value.get("color").unwrap() == "Green")); }); // test and-or: ["tags:bug", ["color:blue", "color:green"]] let query = json!({ @@ -310,20 +312,14 @@ async fn placeholder_test_faceted_search_valid() { .as_array() .unwrap() .iter() - .all(|value| - value + .all(|value| value .get("tags") .unwrap() .as_array() .unwrap() .contains(&Value::String("bug".to_owned())) - && (value - .get("color") - .unwrap() == "blue" - || value - .get("color") - .unwrap() == "Green"))); - + && (value.get("color").unwrap() == "blue" + || value.get("color").unwrap() == "Green"))); }); } @@ -335,7 +331,10 @@ async fn placeholder_test_faceted_search_invalid() { let query = json!({ "facetFilters": ["color:blue"] }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); let body = json!({ "attributesForFaceting": ["color", "tags"] @@ -346,34 +345,52 @@ async fn placeholder_test_faceted_search_invalid() { let query = json!({ "facetFilters": [] }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); // [[]] let query = json!({ "facetFilters": [[]] }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); // ["color:green", []] let query = json!({ "facetFilters": ["color:green", []] }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); // too much depth // [[[]]] let query = json!({ "facetFilters": [[[]]] }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); // [["color:green", ["color:blue"]]] let query = json!({ "facetFilters": [["color:green", ["color:blue"]]] }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); // "color:green" let query = json!({ "facetFilters": "color:green" }); - test_post_get_search!(server, query, |_response, status_code| assert_ne!(status_code, 202)); + test_post_get_search!(server, query, |_response, status_code| assert_ne!( + status_code, + 202 + )); } #[actix_rt::test] @@ -381,9 +398,8 @@ async fn placeholder_test_facet_count() { let mut server = common::Server::test_server().await; // test without facet distribution - let query = json!({ - }); - test_post_get_search!(server, query, |response, _status_code|{ + let query = json!({}); + test_post_get_search!(server, query, |response, _status_code| { assert!(response.get("exhaustiveFacetsCount").is_none()); assert!(response.get("facetsDistribution").is_none()); }); @@ -392,7 +408,7 @@ async fn placeholder_test_facet_count() { let query = json!({ "facetsDistribution": ["color"] }); - test_post_get_search!(server, query.clone(), |_response, status_code|{ + test_post_get_search!(server, query.clone(), |_response, status_code| { assert_eq!(status_code, 400); }); @@ -401,52 +417,109 @@ async fn placeholder_test_facet_count() { }); server.update_all_settings(body).await; // same as before, but now facets are set: - test_post_get_search!(server, query, |response, _status_code|{ + test_post_get_search!(server, query, |response, _status_code| { println!("{}", response); assert!(response.get("exhaustiveFacetsCount").is_some()); - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 1); + assert_eq!( + response + .get("facetsDistribution") + .unwrap() + .as_object() + .unwrap() + .values() + .count(), + 1 + ); }); // searching on color and tags let query = json!({ "facetsDistribution": ["color", "tags"] }); - test_post_get_search!(server, query, |response, _status_code|{ - let facets = response.get("facetsDistribution").unwrap().as_object().unwrap(); + test_post_get_search!(server, query, |response, _status_code| { + let facets = response + .get("facetsDistribution") + .unwrap() + .as_object() + .unwrap(); assert_eq!(facets.values().count(), 2); - assert_ne!(!facets.get("color").unwrap().as_object().unwrap().values().count(), 0); - assert_ne!(!facets.get("tags").unwrap().as_object().unwrap().values().count(), 0); + assert_ne!( + !facets + .get("color") + .unwrap() + .as_object() + .unwrap() + .values() + .count(), + 0 + ); + assert_ne!( + !facets + .get("tags") + .unwrap() + .as_object() + .unwrap() + .values() + .count(), + 0 + ); }); // wildcard let query = json!({ "facetsDistribution": ["*"] }); - test_post_get_search!(server, query, |response, _status_code|{ - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + test_post_get_search!(server, query, |response, _status_code| { + assert_eq!( + response + .get("facetsDistribution") + .unwrap() + .as_object() + .unwrap() + .values() + .count(), + 2 + ); }); // wildcard with other attributes: let query = json!({ "facetsDistribution": ["color", "*"] }); - test_post_get_search!(server, query, |response, _status_code|{ - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + test_post_get_search!(server, query, |response, _status_code| { + assert_eq!( + response + .get("facetsDistribution") + .unwrap() + .as_object() + .unwrap() + .values() + .count(), + 2 + ); }); // empty facet list let query = json!({ "facetsDistribution": [] }); - test_post_get_search!(server, query, |response, _status_code|{ - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 0); + test_post_get_search!(server, query, |response, _status_code| { + assert_eq!( + response + .get("facetsDistribution") + .unwrap() + .as_object() + .unwrap() + .values() + .count(), + 0 + ); }); // attr not set as facet passed: let query = json!({ "facetsDistribution": ["gender"] }); - test_post_get_search!(server, query, |_response, status_code|{ + test_post_get_search!(server, query, |_response, status_code| { assert_eq!(status_code, 400); }); - } #[actix_rt::test] @@ -475,13 +548,15 @@ async fn placeholder_test_sort() { "attributesForFaceting": ["color"] }); server.update_all_settings(body).await; - let query = json!({ }); + let query = json!({}); test_post_get_search!(server, query, |response, _status_code| { let hits = response["hits"].as_array().unwrap(); - hits.iter().map(|v| v["age"].as_u64().unwrap()).fold(0, |prev, cur| { - assert!(cur >= prev); - cur - }); + hits.iter() + .map(|v| v["age"].as_u64().unwrap()) + .fold(0, |prev, cur| { + assert!(cur >= prev); + cur + }); }); let query = json!({ @@ -489,10 +564,12 @@ async fn placeholder_test_sort() { }); test_post_get_search!(server, query, |response, _status_code| { let hits = response["hits"].as_array().unwrap(); - hits.iter().map(|v| v["age"].as_u64().unwrap()).fold(0, |prev, cur| { - assert!(cur >= prev); - cur - }); + hits.iter() + .map(|v| v["age"].as_u64().unwrap()) + .fold(0, |prev, cur| { + assert!(cur >= prev); + cur + }); }); }