From 8cd224899cb079b1254dcf8ef47dc7f251618cb2 Mon Sep 17 00:00:00 2001 From: mpostma Date: Thu, 28 May 2020 19:35:34 +0200 Subject: [PATCH 1/9] move search logic out of search route --- meilisearch-http/src/routes/search.rs | 241 +++++++++++++------------- 1 file changed, 123 insertions(+), 118 deletions(-) diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 0d698eafb..654d50fcf 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -8,7 +8,7 @@ use serde::Deserialize; use serde_json::Value; use crate::error::{Error, FacetCountError, ResponseError}; -use crate::helpers::meilisearch::IndexSearchExt; +use crate::helpers::meilisearch::{IndexSearchExt, SearchResult}; use crate::helpers::Authentication; use crate::routes::IndexParam; use crate::Data; @@ -36,129 +36,134 @@ struct SearchQuery { facets_distribution: Option, } +impl SearchQuery { + fn search(&self, index_uid: &str, data: web::Data) -> Result { + let index = data + .db + .open_index(index_uid) + .ok_or(Error::index_not_found(index_uid))?; + + let reader = data.db.main_read_txn()?; + let schema = index + .main + .schema(&reader)? + .ok_or(Error::internal("Impossible to retrieve the schema"))?; + + let mut search_builder = index.new_search(self.q.clone()); + + if let Some(offset) = self.offset { + search_builder.offset(offset); + } + if let Some(limit) = self.limit { + search_builder.limit(limit); + } + + let available_attributes = schema.displayed_name(); + 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(); + if attributes_to_retrieve.contains("*") { + restricted_attributes = available_attributes.clone(); + } else { + restricted_attributes = HashSet::new(); + for attr in attributes_to_retrieve { + if available_attributes.contains(attr) { + restricted_attributes.insert(attr); + search_builder.add_retrievable_field(attr.to_string()); + } else { + warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); + } + } + } + }, + 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)?); + } + + if let Some(facets) = &self.facets_distribution { + match index.main.attributes_for_faceting(&reader)? { + Some(ref attrs) => { + let field_ids = prepare_facet_list(&facets, &schema, attrs)?; + search_builder.add_facets(field_ids); + }, + None => return Err(FacetCountError::NoFacetSet.into()), + } + } + + if let Some(attributes_to_crop) = &self.attributes_to_crop { + let default_length = self.crop_length.unwrap_or(200); + let mut final_attributes: HashMap = HashMap::new(); + + 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); + 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 => (), + } + } + + search_builder.attributes_to_crop(final_attributes); + } + + if let Some(attributes_to_highlight) = &self.attributes_to_highlight { + let mut final_attributes: HashSet = HashSet::new(); + for attribute in attributes_to_highlight.split(',') { + if attribute == "*" { + for attr in &restricted_attributes { + final_attributes.insert(attr.to_string()); + } + } else { + if available_attributes.contains(attribute) { + final_attributes.insert(attribute.to_string()); + } else { + warn!("The attributes {:?} present in attributesToHighlight parameter doesn't exist", attribute); + } + } + } + + search_builder.attributes_to_highlight(final_attributes); + } + + if let Some(filters) = &self.filters { + search_builder.filters(filters.to_string()); + } + + if let Some(matches) = self.matches { + if matches { + search_builder.get_matches(); + } + } + search_builder.search(&reader) + } +} + #[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_url_query( data: web::Data, path: web::Path, params: web::Query, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; - - let reader = data.db.main_read_txn()?; - let schema = index - .main - .schema(&reader)? - .ok_or(Error::internal("Impossible to retrieve the schema"))?; - - let mut search_builder = index.new_search(params.q.clone()); - - if let Some(offset) = params.offset { - search_builder.offset(offset); - } - if let Some(limit) = params.limit { - search_builder.limit(limit); - } - - let available_attributes = schema.displayed_name(); - let mut restricted_attributes: HashSet<&str>; - match ¶ms.attributes_to_retrieve { - Some(attributes_to_retrieve) => { - let attributes_to_retrieve: HashSet<&str> = attributes_to_retrieve.split(',').collect(); - if attributes_to_retrieve.contains("*") { - restricted_attributes = available_attributes.clone(); - } else { - restricted_attributes = HashSet::new(); - for attr in attributes_to_retrieve { - if available_attributes.contains(attr) { - restricted_attributes.insert(attr); - search_builder.add_retrievable_field(attr.to_string()); - } else { - warn!("The attributes {:?} present in attributesToCrop parameter doesn't exist", attr); - } - } - } - }, - None => { - restricted_attributes = available_attributes.clone(); - } - } - - if let Some(ref facet_filters) = params.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)?); - } - - if let Some(facets) = ¶ms.facets_distribution { - match index.main.attributes_for_faceting(&reader)? { - Some(ref attrs) => { - let field_ids = prepare_facet_list(&facets, &schema, attrs)?; - search_builder.add_facets(field_ids); - }, - None => return Err(FacetCountError::NoFacetSet.into()), - } - } - - if let Some(attributes_to_crop) = ¶ms.attributes_to_crop { - let default_length = params.crop_length.unwrap_or(200); - let mut final_attributes: HashMap = HashMap::new(); - - 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); - 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 => (), - } - } - - search_builder.attributes_to_crop(final_attributes); - } - - if let Some(attributes_to_highlight) = ¶ms.attributes_to_highlight { - let mut final_attributes: HashSet = HashSet::new(); - for attribute in attributes_to_highlight.split(',') { - if attribute == "*" { - for attr in &restricted_attributes { - final_attributes.insert(attr.to_string()); - } - } else { - if available_attributes.contains(attribute) { - final_attributes.insert(attribute.to_string()); - } else { - warn!("The attributes {:?} present in attributesToHighlight parameter doesn't exist", attribute); - } - } - } - - search_builder.attributes_to_highlight(final_attributes); - } - - if let Some(filters) = ¶ms.filters { - search_builder.filters(filters.to_string()); - } - - if let Some(matches) = params.matches { - if matches { - search_builder.get_matches(); - } - } - let search_result = search_builder.search(&reader)?; - + let search_result = params.search(&path.index_uid, data)?; Ok(HttpResponse::Ok().json(search_result)) } From 3e13e728aa37a1ea9e83aa6d7c73ed14f5cafb33 Mon Sep 17 00:00:00 2001 From: mpostma Date: Thu, 28 May 2020 19:37:54 +0200 Subject: [PATCH 2/9] add post method --- meilisearch-http/src/routes/search.rs | 31 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 654d50fcf..4a10ae649 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -3,7 +3,7 @@ use std::collections::{HashSet, HashMap}; use log::warn; use actix_web::web; use actix_web::HttpResponse; -use actix_web_macros::get; +use actix_web_macros::{get, post}; use serde::Deserialize; use serde_json::Value; @@ -36,6 +36,26 @@ struct SearchQuery { facets_distribution: Option, } +#[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")] +async fn search_with_url_query( + data: web::Data, + path: web::Path, + params: web::Query, +) -> Result { + let search_result = params.search(&path.index_uid, data)?; + Ok(HttpResponse::Ok().json(search_result)) +} + +#[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")] +async fn search_with_post( + data: web::Data, + path: web::Path, + params: web::Query, +) -> Result { + let search_result = params.search(&path.index_uid, data)?; + Ok(HttpResponse::Ok().json(search_result)) +} + impl SearchQuery { fn search(&self, index_uid: &str, data: web::Data) -> Result { let index = data @@ -157,15 +177,6 @@ impl SearchQuery { } } -#[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")] -async fn search_with_url_query( - data: web::Data, - path: web::Path, - params: web::Query, -) -> Result { - let search_result = params.search(&path.index_uid, data)?; - Ok(HttpResponse::Ok().json(search_result)) -} /// Parses the incoming string into an array of attributes for which to return a count. It returns /// a Vec of attribute names ascociated with their id. From 940105efb3ad9c5f5884667707ff058e2f96ae52 Mon Sep 17 00:00:00 2001 From: mpostma Date: Thu, 28 May 2020 19:39:38 +0200 Subject: [PATCH 3/9] change cors max age --- meilisearch-http/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/meilisearch-http/src/main.rs b/meilisearch-http/src/main.rs index c60cb2f1a..1782f9126 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -70,6 +70,7 @@ async fn main() -> Result<(), MainError> { Cors::new() .send_wildcard() .allowed_header("x-meili-api-key") + .max_age(86_400) // 24h .finish(), ) .wrap(middleware::Logger::default()) From 6add10b18f0413a490ace409f26907b56697d3f3 Mon Sep 17 00:00:00 2001 From: mpostma Date: Fri, 29 May 2020 17:36:55 +0200 Subject: [PATCH 4/9] add search post route --- meilisearch-http/src/main.rs | 2 +- meilisearch-http/src/routes/search.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/meilisearch-http/src/main.rs b/meilisearch-http/src/main.rs index 1782f9126..0ede01bbb 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -70,7 +70,7 @@ async fn main() -> Result<(), MainError> { Cors::new() .send_wildcard() .allowed_header("x-meili-api-key") - .max_age(86_400) // 24h + //.max_age(86_400) // 24h .finish(), ) .wrap(middleware::Logger::default()) diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 4a10ae649..fd1188c39 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -17,7 +17,8 @@ use meilisearch_core::facets::FacetFilter; use meilisearch_schema::{Schema, FieldId}; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(search_with_url_query); + cfg.service(search_with_post) + .service(search_with_url_query); } #[derive(Deserialize)] @@ -26,10 +27,7 @@ struct SearchQuery { q: String, offset: Option, limit: Option, - attributes_to_retrieve: Option, - attributes_to_crop: Option, - crop_length: Option, - attributes_to_highlight: Option, + attributes_to_retrieve: Option, attributes_to_crop: Option, crop_length: Option, attributes_to_highlight: Option, filters: Option, matches: Option, facet_filters: Option, @@ -50,7 +48,7 @@ async fn search_with_url_query( async fn search_with_post( data: web::Data, path: web::Path, - params: web::Query, + params: web::Json, ) -> Result { let search_result = params.search(&path.index_uid, data)?; Ok(HttpResponse::Ok().json(search_result)) From 0ebf7b6214dcec68a05473199d9806ea1d703cb5 Mon Sep 17 00:00:00 2001 From: mpostma Date: Fri, 29 May 2020 22:16:42 +0200 Subject: [PATCH 5/9] fix CORS config error in actix --- meilisearch-http/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meilisearch-http/src/main.rs b/meilisearch-http/src/main.rs index 0ede01bbb..d7edcce6d 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -69,8 +69,8 @@ async fn main() -> Result<(), MainError> { .wrap( Cors::new() .send_wildcard() - .allowed_header("x-meili-api-key") - //.max_age(86_400) // 24h + .allowed_headers(vec!["content-type","x-meili-api-key"]) + .max_age(86_400) // 24h .finish(), ) .wrap(middleware::Logger::default()) From 26d29783ce318e7b47ea0f455104adf88cf47ce8 Mon Sep 17 00:00:00 2001 From: mpostma Date: Mon, 1 Jun 2020 14:54:12 +0200 Subject: [PATCH 6/9] add tests for post search route --- Cargo.lock | 11 + meilisearch-http/Cargo.toml | 1 + meilisearch-http/tests/common.rs | 7 +- meilisearch-http/tests/index.rs | 2 +- meilisearch-http/tests/search.rs | 876 +++++++++++------- meilisearch-http/tests/search_settings.rs | 14 +- meilisearch-http/tests/settings_stop_words.rs | 4 +- 7 files changed, 576 insertions(+), 339 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de6c9650a..fc0d453bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1604,6 +1604,7 @@ dependencies = [ "serde", "serde_json", "serde_qs", + "serde_url_params", "sha2", "siphasher", "slice-group-by", @@ -2476,6 +2477,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_url_params" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24680ccd1ad7cdee9e8affa70f37d081b3d14d3800d33a28f474d0f7a55f305" +dependencies = [ + "serde", + "url", +] + [[package]] name = "serde_urlencoded" version = "0.6.1" diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 67abfb35b..87862e7d0 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -73,6 +73,7 @@ optional = true [dev-dependencies] tempdir = "0.3.7" tokio = { version = "0.2.18", features = ["macros", "time"] } +serde_url_params = "0.2.0" [dev-dependencies.assert-json-diff] git = "https://github.com/qdequele/assert-json-diff" diff --git a/meilisearch-http/tests/common.rs b/meilisearch-http/tests/common.rs index b53b35bf1..53b58bc95 100644 --- a/meilisearch-http/tests/common.rs +++ b/meilisearch-http/tests/common.rs @@ -252,11 +252,16 @@ impl Server { self.delete_request(&url).await } - pub async fn search(&mut self, query: &str) -> (Value, StatusCode) { + pub async fn search_get(&mut self, query: &str) -> (Value, StatusCode) { let url = format!("/indexes/{}/search?{}", self.uid, query); self.get_request(&url).await } + pub async fn search_post(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/search", self.uid); + self.post_request(&url, body).await + } + pub async fn get_all_updates_status(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/updates", self.uid); self.get_request(&url).await diff --git a/meilisearch-http/tests/index.rs b/meilisearch-http/tests/index.rs index babe0e7aa..e4faaf0a9 100644 --- a/meilisearch-http/tests/index.rs +++ b/meilisearch-http/tests/index.rs @@ -629,7 +629,7 @@ async fn create_index_without_primary_key_and_search() { let query = "q=captain&limit=3"; - let (response, status_code) = server.search(&query).await; + let (response, status_code) = server.search_get(&query).await; assert_eq!(status_code, 200); assert_eq!(response["hits"].as_array().unwrap().len(), 0); } diff --git a/meilisearch-http/tests/search.rs b/meilisearch-http/tests/search.rs index 39bb163eb..a7b59fe22 100644 --- a/meilisearch-http/tests/search.rs +++ b/meilisearch-http/tests/search.rs @@ -6,15 +6,27 @@ use serde_json::Value; mod common; -// Search -// q: Captain -// limit: 3 +macro_rules! test_post_get_search { + ($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => { + let get_query = ::serde_url_params::to_string(&$query).unwrap(); + let ($response, $status_code) = $server.search_get(&get_query).await; + let _ =::std::panic::catch_unwind(|| $block) + .map_err(|e| panic!("panic in get route: {:?}", e.downcast_ref::<&str>().unwrap())); + let ($response, $status_code) = $server.search_post($query).await; + let _ =::std::panic::catch_unwind(|| $block) + .map_err(|e| panic!("panic in post route: {:?}", e.downcast_ref::<&str>().unwrap())); + }; +} + #[actix_rt::test] async fn search_with_limit() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=3"; + let query = json! ({ + "q": "captain", + "limit": 3 + }); let expected = json!([ { @@ -59,8 +71,7 @@ async fn search_with_limit() { "tagline": "When patriots become heroes", "overview": "During World War II, Steve Rogers is a sickly man from Brooklyn who's transformed into super-soldier Captain America to aid in the war effort. Rogers must stop the Red Skull – Adolf Hitler's ruthless head of weaponry, and the leader of an organization that intends to use a mysterious device of untold powers for world domination.", "director": "Joe Johnston", - "producer": "Kevin Feige", - "genres": [ + "producer": "Kevin Feige", "genres": [ "Action", "Adventure", "Science Fiction" @@ -70,20 +81,21 @@ async fn search_with_limit() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with offset -// q: Captain -// limit: 3 -// offset: 1 #[actix_rt::test] async fn search_with_offset() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=3&offset=1"; + let query = json!({ + "q": "Captain", + "limit": 3, + "offset": 1 + }); let expected = json!([ { @@ -140,20 +152,21 @@ async fn search_with_offset() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attribute to highlight all -// q: Captain -// limit: 1 -// attributeToHighlight: * #[actix_rt::test] async fn search_with_attribute_to_highlight_wildcard() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=*"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToHighlight": "*" + }); let expected = json!([ { @@ -192,20 +205,21 @@ async fn search_with_attribute_to_highlight_wildcard() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attribute to highlight title -// q: Captain -// limit: 1 -// attributeToHighlight: title #[actix_rt::test] async fn search_with_attribute_to_highlight_1() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": "title" + }); let expected = json!([ { @@ -244,8 +258,9 @@ async fn search_with_attribute_to_highlight_1() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attribute to highlight title and tagline @@ -257,7 +272,11 @@ async fn search_with_attribute_to_highlight_title_tagline() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title,tagline"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": "title,tagline" + }); let expected = json!([ { @@ -296,20 +315,21 @@ async fn search_with_attribute_to_highlight_title_tagline() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attribute to highlight title and overview -// q: Captain -// limit: 1 -// attributeToHighlight: title,overview #[actix_rt::test] async fn search_with_attribute_to_highlight_title_overview() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title,overview"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": "title,overview" + }); let expected = json!([ { @@ -348,20 +368,21 @@ async fn search_with_attribute_to_highlight_title_overview() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with matches -// q: Captain -// limit: 1 -// matches: true #[actix_rt::test] async fn search_with_matches() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&matches=true"; + let query = json!({ + "q": "Captain", + "limit": 1, + "matches": true + }); let expected = json!([ { @@ -397,21 +418,22 @@ async fn search_with_matches() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with crop -// q: Captain -// limit: 1 -// attributesToCrop: overview -// cropLength: 20 #[actix_rt::test] async fn search_witch_crop() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToCrop=overview&cropLength=20"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToCrop": "overview", + "cropLength": 20 + }); let expected = json!([ { @@ -450,20 +472,21 @@ async fn search_witch_crop() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to retrieve -// q: Captain -// limit: 1 -// attributesToRetrieve: [title,tagline,overview,poster_path] #[actix_rt::test] async fn search_with_attributes_to_retrieve() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,tagline,overview,poster_path"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToRetrieve": "title,tagline,overview,poster_path" + }); let expected = json!([ { @@ -474,20 +497,21 @@ async fn search_with_attributes_to_retrieve() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to retrieve wildcard -// q: Captain -// limit: 1 -// attributesToRetrieve: * #[actix_rt::test] async fn search_with_attributes_to_retrieve_wildcard() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=*"; + let query = json!({ + "q": "Captain", + "limit": 1, + "attributesToRetrieve": "*" + }); let expected = json!([ { @@ -509,8 +533,9 @@ async fn search_with_attributes_to_retrieve_wildcard() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with filter @@ -522,7 +547,12 @@ async fn search_with_filter() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&filters=director%20%3D%20%22Anthony%20Russo%22&limit=3"; + let query = json!({ + "q": "Captain", + "limit": 3, + "filters": "director='Anthony Russo'" + }); + let expected = json!([ { "id": 271110, @@ -576,8 +606,10 @@ async fn search_with_filter() { "vote_count": 10497 } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -598,10 +630,15 @@ async fn search_with_filter() { } ]); - // filters: title = "american pie 2" - let query = "q=american&filters=title%20%3D%20%22american%20pie%202%22"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "limit": 3, + "filters": "title='american pie 2'" + }); + + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -639,10 +676,14 @@ async fn search_with_filter() { "vote_count": 11972 } ]); - // limit: 3, director = "anthony russo" AND (title = "captain america: civil war" OR title = "Captain America: The Winter Soldier") - let query = "q=a&limit=3&filters=director%20%3D%20%22anthony%20russo%22%20AND%20%20(title%20%3D%20%22captain%20america%3A%20civil%20war%22%20OR%20title%20%3D%20%22Captain%20America%3A%20The%20Winter%20Soldier%22)"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "limit": 3, + "filters": "director='Anthony Russo' AND (title='captain america: civil war' OR title='Captain America: The Winter Soldier')" + }); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -698,9 +739,14 @@ async fn search_with_filter() { } ]); // director = "anthony russo" AND (title = "captain america: civil war" OR vote_average > 8.0) - let query = "q=a&limit=3&filters=director%20%3D%20%22anthony%20russo%22%20AND%20%20(title%20%3D%20%22captain%20america%3A%20civil%20war%22%20OR%20vote_average%20%3E%208.0)"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "limit": 3, + "filters": "director='anthony russo' AND (title = 'captain america: civil war' OR vote_average > 8.0)" + }); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([ { @@ -754,15 +800,26 @@ async fn search_with_filter() { "poster_path": "https://image.tmdb.org/t/p/w500/fXepRAYOx1qC3wju7XdDGx60775.jpg" } ]); - // NOT director = "anthony russo" AND vote_average > 7.5 - let query = "q=a&limit=3&filters=NOT%20director%20%3D%20%22anthony%20russo%22%20AND%20vote_average%20%3E%207.5"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + + let query = json!({ + "q": "a", + "limit": 3, + "filters": "NOT director = 'anthony russo' AND vote_average > 7.5" + }); + + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); let expected = json!([]); - let query = "q=a&filters=NOT%20director%20%3D%20%22anthony%20russo%22%20AND%20title%20%20%3D%20%22Avengers%3A%20Endgame%22"; - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + let query = json!({ + "q": "a", + "filters": "NOT director = 'anthony russo' AND title='Avengers: Endgame'" + }); + + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches @@ -775,7 +832,12 @@ async fn search_with_attributes_to_highlight_and_matches() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToHighlight=title,overview&matches=true"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToHighlight": "title,overview", + "matches": true + }); let expected = json!( [ { @@ -828,23 +890,24 @@ async fn search_with_attributes_to_highlight_and_matches() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to highlight and matches and crop -// q: Captain -// limit: 1 -// attributesToHighlight: [title,overview] -// matches: true -// cropLength: 20 -// attributesToCrop: overview #[actix_rt::test] async fn search_with_attributes_to_highlight_and_matches_and_crop() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToCrop=overview&cropLength=20&attributesToHighlight=title,overview&matches=true"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToCrop": "overview", + "cropLength": 20, + "attributesToHighlight": "title,overview", + "matches": true + }); let expected = json!([ { @@ -897,21 +960,22 @@ async fn search_with_attributes_to_highlight_and_matches_and_crop() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with differents attributes -// q: Captain -// limit: 1 -// attributesToRetrieve: [title,producer,director] -// attributesToHighlight: [title] #[actix_rt::test] async fn search_with_differents_attributes() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToHighlight=title"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToHighlight": "title", + }); let expected = json!([ { @@ -924,22 +988,23 @@ async fn search_with_differents_attributes() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } -// Search with attributes to highlight and matches and crop -// q: Captain -// limit: 1 -// attributesToRetrieve: [title,producer,director] -// attributesToCrop: [overview] -// cropLength: 10 #[actix_rt::test] async fn search_with_differents_attributes_2() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=overview&cropLength=10"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "overview", + "cropLength": 10, + }); let expected = json!([ { @@ -952,8 +1017,9 @@ async fn search_with_differents_attributes_2() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -966,7 +1032,12 @@ async fn search_with_differents_attributes_3() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=overview:10"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "overview:10", + }); let expected = json!([ { @@ -979,8 +1050,9 @@ async fn search_with_differents_attributes_3() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -993,7 +1065,12 @@ async fn search_with_differents_attributes_4() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=overview:10,title:0"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "overview:10,title:0", + }); let expected = json!([ { @@ -1007,8 +1084,9 @@ async fn search_with_differents_attributes_4() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1021,7 +1099,12 @@ async fn search_with_differents_attributes_5() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "*,overview:10", + }); let expected = json!([ { @@ -1037,8 +1120,9 @@ async fn search_with_differents_attributes_5() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1052,7 +1136,13 @@ async fn search_with_differents_attributes_6() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10&attributesToHighlight=title"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "*,overview:10", + "attributesToHighlight": "title" + }); let expected = json!([ { @@ -1068,8 +1158,9 @@ async fn search_with_differents_attributes_6() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1083,7 +1174,13 @@ async fn search_with_differents_attributes_7() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10&attributesToHighlight=*"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "*,overview:10", + "attributesToHighlight": "*" + }); let expected = json!([ { @@ -1099,8 +1196,9 @@ async fn search_with_differents_attributes_7() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } // Search with attributes to highlight and matches and crop @@ -1114,7 +1212,13 @@ async fn search_with_differents_attributes_8() { let mut server = common::Server::with_uid("movies"); server.populate_movies().await; - let query = "q=captain&limit=1&attributesToRetrieve=title,producer,director&attributesToCrop=*,overview:10&attributesToHighlight=*,tagline"; + let query = json!({ + "q": "captain", + "limit": 1, + "attributesToRetrieve": "title,producer,director", + "attributesToCrop": "*,overview:10", + "attributesToHighlight": "*,tagline" + }); let expected = json!([ { @@ -1131,8 +1235,9 @@ async fn search_with_differents_attributes_8() { } ]); - let (response, _status_code) = server.search(query).await; - assert_json_eq!(expected, response["hits"].clone(), ordered: false); + test_post_get!(server, query, |response, _status_code| { + assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); + }); } #[actix_rt::test] @@ -1143,112 +1248,147 @@ async fn test_faceted_search_valid() { let body = json!({ "attributesForFaceting": ["color"] }); - server.update_all_settings(body).await; - let query = "q=a&facetFilters=%5B%22color%3Agreen%22%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| value.get("color").unwrap() == "green")); - let query = "q=a&facetFilters=%5B%22color%3Ablue%22%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| value.get("color").unwrap() == "blue")); - // test case insensitive : ["color:Blue"] - let query = "q=a&facetFilters=%5B%22color%3ABlue%22%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| value.get("color").unwrap() == "blue")); + server.update_all_settings(body).await; + + let query = json!({ + "q": "a", + "facetFilters": "[\"color:green\"]" + }); + + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .iter() + .all(|value| value.get("color").unwrap() == "green")); + }); + + let query = json!({ + "q": "a", + "facetFilters": "[[\"color:blue\"]]" + }); + + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .iter() + .all(|value| value.get("color").unwrap() == "blue")); + }); + + let query = json!({ + "q": "a", + "facetFilters": "[\"color:Blue\"]" + }); + + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .iter() + .all(|value| value.get("color").unwrap() == "blue")); + }); // test on arrays: ["tags:bug"] let body = json!({ "attributesForFaceting": ["color", "tags"] }); + server.update_all_settings(body).await; - let query = "q=a&facetFilters=%5B%22tags%3Abug%22%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| value.get("tags").unwrap().as_array().unwrap().contains(&Value::String("bug".to_owned())))); - - // test and: ["color:blue", "tags:bug"] - let query = "q=a&facetFilters=%5B%22color%3Ablue%22,%20%22tags%3Abug%22%20%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .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"]] - let query = "q=a&facetFilters=%5B%5B%22color%3Ablue%22,%20%22color%3Agreen%22%5D%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| - value - .get("color") - .unwrap() == "blue" - || value - .get("color") - .unwrap() == "green")); - // test and-or: ["tags:bug", ["color:blue", "color:green"]] - let query = "q=a&facetFilters=%5B%22color%3Ablue%22,%20%22tags%3Abug%22%20%5D"; - let (response, _status_code) = server.search(query).await; - assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); - assert!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .iter() - .all(|value| - value - .get("tags") + let query = json!({ + "q": "a", + "facetFilters": "[\"tags:bug\"]" + }); + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") .unwrap() .as_array() .unwrap() - .contains(&Value::String("bug".to_owned())) - && (value + .iter() + .all(|value| value.get("tags").unwrap().as_array().unwrap().contains(&Value::String("bug".to_owned())))); + }); + + // test and: ["color:blue", "tags:bug"] + let query = json!({ + "q": "a", + "facetFilters": "[\"color:blue\", \"tags:bug\"]" + }); + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .iter() + .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"]] + let query = json!({ + "q": "a", + "facetFilters": "[[\"color:blue\", \"color:green\"]]" + }); + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .iter() + .all(|value| + value .get("color") .unwrap() == "blue" || value .get("color") - .unwrap() == "green"))); + .unwrap() == "green")); + }); + // test and-or: ["tags:bug", ["color:blue", "color:green"]] + let query = json!({ + "q": "a", + "facetFilters": "[\"tags:bug\", [\"color:blue\", \"color:green\"]]" + }); + test_post_get!(server, query, |response, _status_code| { + assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); + assert!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .iter() + .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"))); + + }); } #[actix_rt::test] @@ -1256,10 +1396,15 @@ async fn test_faceted_search_invalid() { let mut server = common::Server::test_server().await; //no faceted attributes set - let query = "q=a&facetFilters=%5B%22color%3Ablue%22,%20%22tags%3Abug%22%20%5D"; - let (response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "[\"color:blue\"]" + }); + test_post_get!(server, query, |response, status_code| { + + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); let body = json!({ "attributesForFaceting": ["color", "tags"] @@ -1267,37 +1412,61 @@ async fn test_faceted_search_invalid() { server.update_all_settings(body).await; // empty arrays are error // [] - let query = "q=a&facetFilters=%5B%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "[]" + }); + test_post_get!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // [[]] - let query = "q=a&facetFilters=%5B%5B%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "[[]]" + }); + test_post_get!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // ["color:green", []] - let query = "q=a&facetFilters=%5B%22color%3Agreen%22,%20%5B%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "[\"color:green\", []]" + }); + test_post_get!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // too much depth // [[[]]] - let query = "q=a&facetFilters=%5B%5B%5B%5D%5D%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "[[[]]]" + }); + test_post_get!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // [["color:green", ["color:blue"]]] - let query = "q=a&facetFilters=%5B%5B%22color%3Agreen%22,%20%5B%22color%3Ablue%22%5D%5D%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "[[\"color:green\", [\"color:blue\"]]]" + }); + test_post_get!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); // "color:green" - let query = "q=a&facetFilters=%22color%3Agreen%22"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); - assert_eq!(response["errorCode"], "invalid_facet"); + let query = json!({ + "q": "a", + "facetFilters": "\"color:green\"" + }); + test_post_get!(server, query, |response, status_code| { + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_facet"); + }); } #[actix_rt::test] @@ -1305,57 +1474,94 @@ async fn test_facet_count() { let mut server = common::Server::test_server().await; // test without facet distribution - let query = "q=a"; - let (response, _status_code) = server.search(query).await; - assert!(response.get("exhaustiveFacetsCount").is_none()); - assert!(response.get("facetsDistribution").is_none()); + let query = json!({ + "q": "a", + }); + test_post_get!(server, query, |response, _status_code|{ + assert!(response.get("exhaustiveFacetsCount").is_none()); + assert!(response.get("facetsDistribution").is_none()); + }); // test no facets set, search on color - let query = "q=a&facetsDistribution=%5B%22color%22%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": "[\"color\"]" + }); + test_post_get!(server, query.clone(), |_response, status_code|{ + assert_eq!(status_code, 400); + }); let body = json!({ "attributesForFaceting": ["color", "tags"] }); server.update_all_settings(body).await; // same as before, but now facets are set: - let (response, _status_code) = server.search(query).await; - assert!(response.get("exhaustiveFacetsCount").is_some()); - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 1); + test_post_get!(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); + }); // searching on color and tags - let query = "q=a&facetsDistribution=%5B%22color%22,%20%22tags%22%5D"; - let (response, _status_code) = server.search(query).await; - let facets = response.get("facetsDistribution").unwrap().as_object().unwrap(); - eprintln!("response: {:#?}", response); - 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); + let query = json!({ + "q": "a", + "facetsDistribution": "[\"color\", \"tags\"]" + }); + test_post_get!(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); + }); // wildcard - let query = "q=a&facetsDistribution=%5B%22*%22%5D"; - let (response, _status_code) = server.search(query).await; - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + let query = json!({ + "q": "a", + "facetsDistribution": "[\"*\"]" + }); + test_post_get!(server, query, |response, _status_code|{ + assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + }); // wildcard with other attributes: - let query = "q=a&facetsDistribution=%5B%22color%22,%20%22*%22%5D"; - let (response, _status_code) = server.search(query).await; - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + let query = json!({ + "q": "a", + "facetsDistribution": "[\"color\", \"*\"]" + }); + test_post_get!(server, query, |response, _status_code|{ + assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 2); + }); // empty facet list - let query = "q=a&facetsDistribution=%5B%5D"; - let (response, _status_code) = server.search(query).await; - assert_eq!(response.get("facetsDistribution").unwrap().as_object().unwrap().values().count(), 0); + let query = json!({ + "q": "a", + "facetsDistribution": "[]" + }); + test_post_get!(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 = "q=a&facetsDistribution=%5B%22gender%22%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": "[\"gender\"]" + }); + test_post_get!(server, query, |_response, status_code|{ + assert_eq!(status_code, 400); + }); // string instead of array: - let query = "q=a&faceDistribution=%22gender%22"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": "\"color\"" + }); + test_post_get!(server, query, |_response, status_code|{ + assert_eq!(status_code, 400); + }); // invalid value in array: - let query = "q=a&facetsDistribution=%5B%22color%22,%20true%5D"; - let (_response, status_code) = server.search(query).await; - assert_eq!(status_code, 400); + let query = json!({ + "q": "a", + "facetsDistribution": "[\"color\", true]" + + }); + test_post_get!(server, query, |_response, status_code|{ + assert_eq!(status_code, 400); + }); } #[actix_rt::test] @@ -1381,52 +1587,66 @@ async fn highlight_cropped_text() { server.add_or_replace_multiple_documents(doc).await; // tests from #680 - let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=30"; - let (response, _status_code) = server.search(query).await; + //let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=30"; + let query = json!({ + "q": "insert", + "attributesToHighlight": "*", + "attributesToCrop": "body", + "cropLength": 30, + }); let expected_response = "that, try the following: \n1. insert your trip\n2. google your"; - assert_eq!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .get(0) - .unwrap() - .as_object() - .unwrap() - .get("_formatted") - .unwrap() - .as_object() - .unwrap() - .get("body") - .unwrap() - , &Value::String(expected_response.to_owned())); + test_post_get!(server, query, |response, _status_code|{ + assert_eq!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .get(0) + .unwrap() + .as_object() + .unwrap() + .get("_formatted") + .unwrap() + .as_object() + .unwrap() + .get("body") + .unwrap() + , &Value::String(expected_response.to_owned())); + }); - let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=80"; - let (response, _status_code) = server.search(query).await; + //let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=80"; + let query = json!({ + "q": "insert", + "attributesToHighlight": "*", + "attributesToCrop": "body", + "cropLength": 80, + }); let expected_response = "well, it may not work like that, try the following: \n1. insert your trip\n2. google your `searchQuery`\n3. find a solution \n> say hello"; - assert_eq!(response - .get("hits") - .unwrap() - .as_array() - .unwrap() - .get(0) - .unwrap() - .as_object() - .unwrap() - .get("_formatted") - .unwrap() - .as_object() - .unwrap() - .get("body") - .unwrap() - , &Value::String(expected_response.to_owned())); + test_post_get!(server, query, |response, _status_code| { + assert_eq!(response + .get("hits") + .unwrap() + .as_array() + .unwrap() + .get(0) + .unwrap() + .as_object() + .unwrap() + .get("_formatted") + .unwrap() + .as_object() + .unwrap() + .get("body") + .unwrap() + , &Value::String(expected_response.to_owned())); + }); } #[actix_rt::test] async fn well_formated_error_with_bad_request_params() { let mut server = common::Server::with_uid("test"); let query = "foo=bar"; - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert!(response.get("message").is_some()); assert!(response.get("errorCode").is_some()); assert!(response.get("errorType").is_some()); diff --git a/meilisearch-http/tests/search_settings.rs b/meilisearch-http/tests/search_settings.rs index 6effbdda0..1c8237ea8 100644 --- a/meilisearch-http/tests/search_settings.rs +++ b/meilisearch-http/tests/search_settings.rs @@ -106,7 +106,7 @@ async fn search_with_settings_basic() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -212,7 +212,7 @@ async fn search_with_settings_stop_words() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -323,7 +323,7 @@ async fn search_with_settings_synonyms() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -429,7 +429,7 @@ async fn search_with_settings_ranking_rules() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -534,7 +534,7 @@ async fn search_with_settings_searchable_attributes() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -604,7 +604,7 @@ async fn search_with_settings_displayed_attributes() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } @@ -674,6 +674,6 @@ async fn search_with_settings_searchable_attributes_2() { } ]); - let (response, _status_code) = server.search(query).await; + let (response, _status_code) = server.search_get(query).await; assert_json_eq!(expect, response["hits"].clone(), ordered: false); } diff --git a/meilisearch-http/tests/settings_stop_words.rs b/meilisearch-http/tests/settings_stop_words.rs index 9204b2419..2af770975 100644 --- a/meilisearch-http/tests/settings_stop_words.rs +++ b/meilisearch-http/tests/settings_stop_words.rs @@ -45,12 +45,12 @@ async fn add_documents_and_stop_words() { // 3 - Search for a document with stop words - let (response, _status_code) = server.search("q=the%20mask").await; + let (response, _status_code) = server.search_get("q=the%20mask").await; assert!(!response["hits"].as_array().unwrap().is_empty()); // 4 - Search for documents with *only* stop words - let (response, _status_code) = server.search("q=the%20of").await; + let (response, _status_code) = server.search_get("q=the%20of").await; assert!(response["hits"].as_array().unwrap().is_empty()); // 5 - Delete all stop words From 2d31371975aa578cf67fcef01b149f18f71799c2 Mon Sep 17 00:00:00 2001 From: mpostma Date: Mon, 1 Jun 2020 14:57:26 +0200 Subject: [PATCH 7/9] fix style --- errors.yaml | 51 +++++++++++++++++++++++++++ meilisearch-http/Cargo.toml | 1 - meilisearch-http/src/routes/search.rs | 1 - 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 errors.yaml diff --git a/errors.yaml b/errors.yaml new file mode 100644 index 000000000..718796d57 --- /dev/null +++ b/errors.yaml @@ -0,0 +1,51 @@ +--- +errors: + - code: create_index + description: "Error relatice to index creation, check out our guide on [index creation](link.com)" + - code: existing_index + description: "An index with this name already exists, check out our guide on [index creation](link.com)" + - code: invalid_index_uid + description: "The provided index formatting is wrong, check out our guide on [index creation](link.com)" + - code: open_index + description: "An error occured while trying to open the requested index, ..." + - code: invalid_state + description: "" + - code: missing_primary_key + description: "" + - code: primary_key_already_present + description: "" + - code: max_field_limit_exceeded + description: "" + - code: missing_document_id + description: "" + - code: invalid_facet + description: "" + - code: invalid_filter + description: "" + - code: bad_parameter + description: "" + - code: bad_request + description: "" + - code: document_not_found + description: "" + - code: internal + description: "" + - code: invalid_token + description: "" + - code: maintenance + description: "" + - code: missing_authorization_header + description: "" + - code: missing_header + description: "" + - code: not_found + description: "" + - code: payload_too_large + description: "" + - code: retrieve_document + description: "" + - code: search_error + description: "" + - code: unsupported_media_type + description: "" +... diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 87862e7d0..67abfb35b 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -73,7 +73,6 @@ optional = true [dev-dependencies] tempdir = "0.3.7" tokio = { version = "0.2.18", features = ["macros", "time"] } -serde_url_params = "0.2.0" [dev-dependencies.assert-json-diff] git = "https://github.com/qdequele/assert-json-diff" diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index fd1188c39..0cefc0511 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -175,7 +175,6 @@ impl SearchQuery { } } - /// Parses the incoming string into an array of attributes for which to return a count. It returns /// a Vec of attribute names ascociated with their id. /// From f6795775e2b0d873b985be463210194f0b75901a Mon Sep 17 00:00:00 2001 From: mpostma Date: Mon, 1 Jun 2020 15:00:46 +0200 Subject: [PATCH 8/9] update changelog --- CHANGELOG.md | 1 + errors.yaml | 51 --------------------------------------------------- 2 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 errors.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ccab2d5..f6f40e3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Add support for error code reporting (#703) - Allow the dashboard to query private servers (#732) - Add telemetry (#720) + - Add post route for search (#735) ## v0.10.1 diff --git a/errors.yaml b/errors.yaml deleted file mode 100644 index 718796d57..000000000 --- a/errors.yaml +++ /dev/null @@ -1,51 +0,0 @@ ---- -errors: - - code: create_index - description: "Error relatice to index creation, check out our guide on [index creation](link.com)" - - code: existing_index - description: "An index with this name already exists, check out our guide on [index creation](link.com)" - - code: invalid_index_uid - description: "The provided index formatting is wrong, check out our guide on [index creation](link.com)" - - code: open_index - description: "An error occured while trying to open the requested index, ..." - - code: invalid_state - description: "" - - code: missing_primary_key - description: "" - - code: primary_key_already_present - description: "" - - code: max_field_limit_exceeded - description: "" - - code: missing_document_id - description: "" - - code: invalid_facet - description: "" - - code: invalid_filter - description: "" - - code: bad_parameter - description: "" - - code: bad_request - description: "" - - code: document_not_found - description: "" - - code: internal - description: "" - - code: invalid_token - description: "" - - code: maintenance - description: "" - - code: missing_authorization_header - description: "" - - code: missing_header - description: "" - - code: not_found - description: "" - - code: payload_too_large - description: "" - - code: retrieve_document - description: "" - - code: search_error - description: "" - - code: unsupported_media_type - description: "" -... From e5079004e131cebc5be64de9e1f2d45324145046 Mon Sep 17 00:00:00 2001 From: mpostma Date: Mon, 1 Jun 2020 20:22:18 +0200 Subject: [PATCH 9/9] adds SearchQueryPost --- meilisearch-http/Cargo.toml | 1 + meilisearch-http/src/routes/search.rs | 50 +++++- meilisearch-http/tests/search.rs | 245 ++++++++++++++------------ 3 files changed, 176 insertions(+), 120 deletions(-) diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 67abfb35b..87862e7d0 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -73,6 +73,7 @@ optional = true [dev-dependencies] tempdir = "0.3.7" tokio = { version = "0.2.18", features = ["macros", "time"] } +serde_url_params = "0.2.0" [dev-dependencies.assert-json-diff] git = "https://github.com/qdequele/assert-json-diff" diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 0cefc0511..9fff7d6d2 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -4,7 +4,7 @@ use log::warn; use actix_web::web; use actix_web::HttpResponse; use actix_web_macros::{get, post}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::error::{Error, FacetCountError, ResponseError}; @@ -21,13 +21,16 @@ pub fn services(cfg: &mut web::ServiceConfig) { .service(search_with_url_query); } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -struct SearchQuery { +pub struct SearchQuery { q: String, offset: Option, limit: Option, - attributes_to_retrieve: Option, attributes_to_crop: Option, crop_length: Option, attributes_to_highlight: Option, + attributes_to_retrieve: Option, + attributes_to_crop: Option, + crop_length: Option, + attributes_to_highlight: Option, filters: Option, matches: Option, facet_filters: Option, @@ -44,13 +47,48 @@ async fn search_with_url_query( Ok(HttpResponse::Ok().json(search_result)) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct SearchQueryPost { + q: String, + offset: Option, + limit: Option, + attributes_to_retrieve: Option>, + attributes_to_crop: Option>, + crop_length: Option, + attributes_to_highlight: Option>, + filters: Option, + matches: Option, + facet_filters: Option, + facets_distribution: Option>, +} + +impl From for SearchQuery { + fn from(other: SearchQueryPost) -> SearchQuery { + SearchQuery { + q: other.q, + offset: other.offset, + limit: other.limit, + attributes_to_retrieve: other.attributes_to_retrieve.map(|attrs| attrs.join(",")), + attributes_to_crop: other.attributes_to_crop.map(|attrs| attrs.join(",")), + crop_length: other.crop_length, + attributes_to_highlight: other.attributes_to_highlight.map(|attrs| attrs.join(",")), + filters: other.filters, + matches: other.matches, + facet_filters: other.facet_filters.map(|f| f.to_string()), + facets_distribution: other.facets_distribution.map(|f| format!("{:?}", f)), + } + } +} + #[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_post( data: web::Data, path: web::Path, - params: web::Json, + params: web::Json, ) -> Result { - let search_result = params.search(&path.index_uid, data)?; + let query: SearchQuery = params.0.into(); + let search_result = query.search(&path.index_uid, data)?; Ok(HttpResponse::Ok().json(search_result)) } diff --git a/meilisearch-http/tests/search.rs b/meilisearch-http/tests/search.rs index a7b59fe22..0ced0568f 100644 --- a/meilisearch-http/tests/search.rs +++ b/meilisearch-http/tests/search.rs @@ -1,5 +1,6 @@ use std::convert::Into; +use meilisearch_http::routes::search::{SearchQuery, SearchQueryPost}; use assert_json_diff::assert_json_eq; use serde_json::json; use serde_json::Value; @@ -8,7 +9,9 @@ mod common; macro_rules! test_post_get_search { ($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => { - let get_query = ::serde_url_params::to_string(&$query).unwrap(); + let post_query: SearchQueryPost = serde_json::from_str(&$query.clone().to_string()).unwrap(); + let get_query: SearchQuery = post_query.into(); + let get_query = ::serde_url_params::to_string(&get_query).unwrap(); let ($response, $status_code) = $server.search_get(&get_query).await; let _ =::std::panic::catch_unwind(|| $block) .map_err(|e| panic!("panic in get route: {:?}", e.downcast_ref::<&str>().unwrap())); @@ -81,7 +84,7 @@ async fn search_with_limit() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -152,7 +155,7 @@ async fn search_with_offset() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -165,7 +168,7 @@ async fn search_with_attribute_to_highlight_wildcard() { let query = json!({ "q": "Captain", "limit": 1, - "attributesToHighlight": "*" + "attributesToHighlight": ["*"] }); let expected = json!([ @@ -205,7 +208,7 @@ async fn search_with_attribute_to_highlight_wildcard() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -218,7 +221,7 @@ async fn search_with_attribute_to_highlight_1() { let query = json!({ "q": "captain", "limit": 1, - "attributesToHighlight": "title" + "attributesToHighlight": ["title"] }); let expected = json!([ @@ -258,7 +261,7 @@ async fn search_with_attribute_to_highlight_1() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -275,7 +278,7 @@ async fn search_with_attribute_to_highlight_title_tagline() { let query = json!({ "q": "captain", "limit": 1, - "attributesToHighlight": "title,tagline" + "attributesToHighlight": ["title","tagline"] }); let expected = json!([ @@ -315,7 +318,7 @@ async fn search_with_attribute_to_highlight_title_tagline() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -328,7 +331,7 @@ async fn search_with_attribute_to_highlight_title_overview() { let query = json!({ "q": "captain", "limit": 1, - "attributesToHighlight": "title,overview" + "attributesToHighlight": ["title","overview"] }); let expected = json!([ @@ -368,7 +371,7 @@ async fn search_with_attribute_to_highlight_title_overview() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -418,7 +421,7 @@ async fn search_with_matches() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -431,7 +434,7 @@ async fn search_witch_crop() { let query = json!({ "q": "Captain", "limit": 1, - "attributesToCrop": "overview", + "attributesToCrop": ["overview"], "cropLength": 20 }); @@ -472,7 +475,7 @@ async fn search_witch_crop() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -485,7 +488,7 @@ async fn search_with_attributes_to_retrieve() { let query = json!({ "q": "Captain", "limit": 1, - "attributesToRetrieve": "title,tagline,overview,poster_path" + "attributesToRetrieve": ["title","tagline","overview","poster_path"], }); let expected = json!([ @@ -497,7 +500,7 @@ async fn search_with_attributes_to_retrieve() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -510,7 +513,7 @@ async fn search_with_attributes_to_retrieve_wildcard() { let query = json!({ "q": "Captain", "limit": 1, - "attributesToRetrieve": "*" + "attributesToRetrieve": ["*"], }); let expected = json!([ @@ -533,7 +536,7 @@ async fn search_with_attributes_to_retrieve_wildcard() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -607,7 +610,7 @@ async fn search_with_filter() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); @@ -636,7 +639,7 @@ async fn search_with_filter() { "filters": "title='american pie 2'" }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); @@ -681,7 +684,7 @@ async fn search_with_filter() { "limit": 3, "filters": "director='Anthony Russo' AND (title='captain america: civil war' OR title='Captain America: The Winter Soldier')" }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); @@ -744,7 +747,7 @@ async fn search_with_filter() { "limit": 3, "filters": "director='anthony russo' AND (title = 'captain america: civil war' OR vote_average > 8.0)" }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); @@ -807,7 +810,7 @@ async fn search_with_filter() { "filters": "NOT director = 'anthony russo' AND vote_average > 7.5" }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); @@ -817,7 +820,7 @@ async fn search_with_filter() { "filters": "NOT director = 'anthony russo' AND title='Avengers: Endgame'" }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -835,8 +838,8 @@ async fn search_with_attributes_to_highlight_and_matches() { let query = json!({ "q": "captain", "limit": 1, - "attributesToHighlight": "title,overview", - "matches": true + "attributesToHighlight": ["title","overview"], + "matches": true, }); let expected = json!( [ @@ -890,7 +893,7 @@ async fn search_with_attributes_to_highlight_and_matches() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -903,9 +906,9 @@ async fn search_with_attributes_to_highlight_and_matches_and_crop() { let query = json!({ "q": "captain", "limit": 1, - "attributesToCrop": "overview", + "attributesToCrop": ["overview"], "cropLength": 20, - "attributesToHighlight": "title,overview", + "attributesToHighlight": ["title","overview"], "matches": true }); @@ -960,7 +963,7 @@ async fn search_with_attributes_to_highlight_and_matches_and_crop() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -973,8 +976,8 @@ async fn search_with_differents_attributes() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToHighlight": "title", + "attributesToRetrieve": ["title","producer","director"], + "attributesToHighlight": ["title"], }); let expected = json!([ @@ -988,7 +991,7 @@ async fn search_with_differents_attributes() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1001,8 +1004,8 @@ async fn search_with_differents_attributes_2() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "overview", + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["overview"], "cropLength": 10, }); @@ -1017,7 +1020,7 @@ async fn search_with_differents_attributes_2() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1035,8 +1038,8 @@ async fn search_with_differents_attributes_3() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "overview:10", + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["overview:10"], }); let expected = json!([ @@ -1050,7 +1053,7 @@ async fn search_with_differents_attributes_3() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1068,8 +1071,8 @@ async fn search_with_differents_attributes_4() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "overview:10,title:0", + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["overview:10","title:0"], }); let expected = json!([ @@ -1084,7 +1087,7 @@ async fn search_with_differents_attributes_4() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1102,8 +1105,8 @@ async fn search_with_differents_attributes_5() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "*,overview:10", + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], }); let expected = json!([ @@ -1120,7 +1123,7 @@ async fn search_with_differents_attributes_5() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1139,9 +1142,9 @@ async fn search_with_differents_attributes_6() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "*,overview:10", - "attributesToHighlight": "title" + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + "attributesToHighlight": ["title"], }); let expected = json!([ @@ -1158,7 +1161,7 @@ async fn search_with_differents_attributes_6() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1177,9 +1180,9 @@ async fn search_with_differents_attributes_7() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "*,overview:10", - "attributesToHighlight": "*" + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + "attributesToHighlight": ["*"], }); let expected = json!([ @@ -1196,7 +1199,7 @@ async fn search_with_differents_attributes_7() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1215,9 +1218,9 @@ async fn search_with_differents_attributes_8() { let query = json!({ "q": "captain", "limit": 1, - "attributesToRetrieve": "title,producer,director", - "attributesToCrop": "*,overview:10", - "attributesToHighlight": "*,tagline" + "attributesToRetrieve": ["title","producer","director"], + "attributesToCrop": ["*","overview:10"], + "attributesToHighlight": ["*","tagline"], }); let expected = json!([ @@ -1235,7 +1238,7 @@ async fn search_with_differents_attributes_8() { } ]); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_json_eq!(expected.clone(), response["hits"].clone(), ordered: false); }); } @@ -1253,10 +1256,10 @@ async fn test_faceted_search_valid() { let query = json!({ "q": "a", - "facetFilters": "[\"color:green\"]" + "facetFilters": ["color:green"] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1269,10 +1272,10 @@ async fn test_faceted_search_valid() { let query = json!({ "q": "a", - "facetFilters": "[[\"color:blue\"]]" + "facetFilters": [["color:blue"]] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1285,10 +1288,10 @@ async fn test_faceted_search_valid() { let query = json!({ "q": "a", - "facetFilters": "[\"color:Blue\"]" + "facetFilters": ["color:Blue"] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1308,9 +1311,9 @@ async fn test_faceted_search_valid() { let query = json!({ "q": "a", - "facetFilters": "[\"tags:bug\"]" + "facetFilters": ["tags:bug"] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1324,9 +1327,9 @@ async fn test_faceted_search_valid() { // test and: ["color:blue", "tags:bug"] let query = json!({ "q": "a", - "facetFilters": "[\"color:blue\", \"tags:bug\"]" + "facetFilters": ["color:blue", "tags:bug"] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1343,9 +1346,9 @@ async fn test_faceted_search_valid() { // test or: [["color:blue", "color:green"]] let query = json!({ "q": "a", - "facetFilters": "[[\"color:blue\", \"color:green\"]]" + "facetFilters": [["color:blue", "color:green"]] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1364,9 +1367,9 @@ async fn test_faceted_search_valid() { // test and-or: ["tags:bug", ["color:blue", "color:green"]] let query = json!({ "q": "a", - "facetFilters": "[\"tags:bug\", [\"color:blue\", \"color:green\"]]" + "facetFilters": ["tags:bug", ["color:blue", "color:green"]] }); - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert!(!response.get("hits").unwrap().as_array().unwrap().is_empty()); assert!(response .get("hits") @@ -1398,9 +1401,10 @@ async fn test_faceted_search_invalid() { //no faceted attributes set let query = json!({ "q": "a", - "facetFilters": "[\"color:blue\"]" + "facetFilters": ["color:blue"] }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); @@ -1414,27 +1418,31 @@ async fn test_faceted_search_invalid() { // [] let query = json!({ "q": "a", - "facetFilters": "[]" + "facetFilters": [] }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); }); // [[]] let query = json!({ "q": "a", - "facetFilters": "[[]]" + "facetFilters": [[]] }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); }); + // ["color:green", []] let query = json!({ "q": "a", - "facetFilters": "[\"color:green\", []]" + "facetFilters": ["color:green", []] }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); }); @@ -1443,27 +1451,32 @@ async fn test_faceted_search_invalid() { // [[[]]] let query = json!({ "q": "a", - "facetFilters": "[[[]]]" + "facetFilters": [[[]]] }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); }); + // [["color:green", ["color:blue"]]] let query = json!({ "q": "a", - "facetFilters": "[[\"color:green\", [\"color:blue\"]]]" + "facetFilters": [["color:green", ["color:blue"]]] }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); }); + // "color:green" let query = json!({ "q": "a", - "facetFilters": "\"color:green\"" + "facetFilters": "color:green" }); - test_post_get!(server, query, |response, status_code| { + + test_post_get_search!(server, query, |response, status_code| { assert_eq!(status_code, 400); assert_eq!(response["errorCode"], "invalid_facet"); }); @@ -1477,7 +1490,7 @@ async fn test_facet_count() { let query = json!({ "q": "a", }); - test_post_get!(server, query, |response, _status_code|{ + test_post_get_search!(server, query, |response, _status_code|{ assert!(response.get("exhaustiveFacetsCount").is_none()); assert!(response.get("facetsDistribution").is_none()); }); @@ -1485,9 +1498,9 @@ async fn test_facet_count() { // test no facets set, search on color let query = json!({ "q": "a", - "facetsDistribution": "[\"color\"]" + "facetsDistribution": ["color"] }); - test_post_get!(server, query.clone(), |_response, status_code|{ + test_post_get_search!(server, query.clone(), |_response, status_code|{ assert_eq!(status_code, 400); }); @@ -1496,7 +1509,7 @@ async fn test_facet_count() { }); server.update_all_settings(body).await; // same as before, but now facets are set: - test_post_get!(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); @@ -1504,9 +1517,9 @@ async fn test_facet_count() { // searching on color and tags let query = json!({ "q": "a", - "facetsDistribution": "[\"color\", \"tags\"]" + "facetsDistribution": ["color", "tags"] }); - test_post_get!(server, query, |response, _status_code|{ + 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); @@ -1515,53 +1528,57 @@ async fn test_facet_count() { // wildcard let query = json!({ "q": "a", - "facetsDistribution": "[\"*\"]" + "facetsDistribution": ["*"] }); - test_post_get!(server, query, |response, _status_code|{ + 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!({ "q": "a", - "facetsDistribution": "[\"color\", \"*\"]" + "facetsDistribution": ["color", "*"] }); - test_post_get!(server, query, |response, _status_code|{ + 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!({ "q": "a", - "facetsDistribution": "[]" + "facetsDistribution": [] }); - test_post_get!(server, query, |response, _status_code|{ + 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!({ "q": "a", - "facetsDistribution": "[\"gender\"]" + "facetsDistribution": ["gender"] }); - test_post_get!(server, query, |_response, status_code|{ + test_post_get_search!(server, query, |_response, status_code|{ assert_eq!(status_code, 400); }); + +} + +#[actix_rt::test] +#[should_panic] +async fn test_bad_facet_distribution() { + let mut server = common::Server::test_server().await; // string instead of array: let query = json!({ "q": "a", - "facetsDistribution": "\"color\"" - }); - test_post_get!(server, query, |_response, status_code|{ - assert_eq!(status_code, 400); + "facetsDistribution": "color" }); + test_post_get_search!(server, query, |_response, _status_code| {}); + // invalid value in array: let query = json!({ "q": "a", - "facetsDistribution": "[\"color\", true]" - - }); - test_post_get!(server, query, |_response, status_code|{ - assert_eq!(status_code, 400); + "facetsDistribution": ["color", true] }); + test_post_get_search!(server, query, |_response, _status_code| {}); } #[actix_rt::test] @@ -1590,12 +1607,12 @@ async fn highlight_cropped_text() { //let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=30"; let query = json!({ "q": "insert", - "attributesToHighlight": "*", - "attributesToCrop": "body", + "attributesToHighlight": ["*"], + "attributesToCrop": ["body"], "cropLength": 30, }); let expected_response = "that, try the following: \n1. insert your trip\n2. google your"; - test_post_get!(server, query, |response, _status_code|{ + test_post_get_search!(server, query, |response, _status_code|{ assert_eq!(response .get("hits") .unwrap() @@ -1617,12 +1634,12 @@ async fn highlight_cropped_text() { //let query = "q=insert&attributesToHighlight=*&attributesToCrop=body&cropLength=80"; let query = json!({ "q": "insert", - "attributesToHighlight": "*", - "attributesToCrop": "body", + "attributesToHighlight": ["*"], + "attributesToCrop": ["body"], "cropLength": 80, }); let expected_response = "well, it may not work like that, try the following: \n1. insert your trip\n2. google your `searchQuery`\n3. find a solution \n> say hello"; - test_post_get!(server, query, |response, _status_code| { + test_post_get_search!(server, query, |response, _status_code| { assert_eq!(response .get("hits") .unwrap()