diff --git a/Cargo.lock b/Cargo.lock index 3abc34c71..3dfc74f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1914,7 +1914,8 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" version = "2.1.3" -source = "git+https://github.com/pest-parser/pest.git?rev=51fd1d49f1041f7839975664ef71fe15c7dcaf67#51fd1d49f1041f7839975664ef71fe15c7dcaf67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ "ucd-trie", ] @@ -1922,8 +1923,7 @@ dependencies = [ [[package]] name = "pest" version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +source = "git+https://github.com/pest-parser/pest.git?rev=51fd1d49f1041f7839975664ef71fe15c7dcaf67#51fd1d49f1041f7839975664ef71fe15c7dcaf67" dependencies = [ "ucd-trie", ] diff --git a/meilisearch-http/src/data.rs b/meilisearch-http/src/data.rs index ae90e2a03..b552e7f6e 100644 --- a/meilisearch-http/src/data.rs +++ b/meilisearch-http/src/data.rs @@ -3,9 +3,10 @@ use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; -use meilisearch_core::{Database, DatabaseOptions}; +use meilisearch_core::{Database, DatabaseOptions, Index}; use sha2::Digest; +use crate::error::{Error as MSError, ResponseError}; use crate::index_update_callback; use crate::option::Opt; @@ -102,4 +103,60 @@ impl Data { Ok(data) } + + fn create_index(&self, uid: &str) -> Result { + if !uid + .chars() + .all(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_') + { + return Err(MSError::InvalidIndexUid.into()); + } + + let created_index = self.db.create_index(&uid).map_err(|e| match e { + meilisearch_core::Error::IndexAlreadyExists => e.into(), + _ => ResponseError::from(MSError::create_index(e)), + })?; + + self.db.main_write::<_, _, ResponseError>(|mut writer| { + created_index.main.put_name(&mut writer, uid)?; + + created_index + .main + .created_at(&writer)? + .ok_or(MSError::internal("Impossible to read created at"))?; + + created_index + .main + .updated_at(&writer)? + .ok_or(MSError::internal("Impossible to read updated at"))?; + Ok(()) + })?; + + Ok(created_index) + } + + pub fn get_or_create_index(&self, uid: &str, f: F) -> Result + where + F: FnOnce(&Index) -> Result, + { + let mut index_has_been_created = false; + + let index = match self.db.open_index(&uid) { + Some(index) => index, + None => { + index_has_been_created = true; + self.create_index(&uid)? + } + }; + + match f(&index) { + Ok(r) => Ok(r), + Err(err) => { + if index_has_been_created { + let _ = self.db.delete_index(&uid); + } + Err(err) + } + } + } } diff --git a/meilisearch-http/src/routes/document.rs b/meilisearch-http/src/routes/document.rs index 38e333aab..e8fa0f646 100644 --- a/meilisearch-http/src/routes/document.rs +++ b/meilisearch-http/src/routes/document.rs @@ -45,7 +45,8 @@ async fn get_document( let reader = data.db.main_read_txn()?; - let internal_id = index.main + let internal_id = index + .main .external_to_internal_docid(&reader, &path.document_id)? .ok_or(Error::document_not_found(&path.document_id))?; @@ -166,47 +167,41 @@ async fn update_multiple_documents( body: web::Json>, is_partial: bool, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let reader = data.db.main_read_txn()?; - let reader = data.db.main_read_txn()?; + let mut schema = index + .main + .schema(&reader)? + .ok_or(meilisearch_core::Error::SchemaMissing)?; - let mut schema = index - .main - .schema(&reader)? - .ok_or(meilisearch_core::Error::SchemaMissing)?; + if schema.primary_key().is_none() { + let id = match ¶ms.primary_key { + Some(id) => id.to_string(), + None => body + .first() + .and_then(find_primary_key) + .ok_or(meilisearch_core::Error::MissingPrimaryKey)?, + }; - if schema.primary_key().is_none() { - let id = match ¶ms.primary_key { - Some(id) => id.to_string(), - None => body - .first() - .and_then(find_primary_key) - .ok_or(meilisearch_core::Error::MissingPrimaryKey)? + schema.set_primary_key(&id).map_err(Error::bad_request)?; + + data.db.main_write(|w| index.main.put_schema(w, &schema))?; + } + + let mut document_addition = if is_partial { + index.documents_partial_addition() + } else { + index.documents_addition() }; - schema - .set_primary_key(&id) - .map_err(Error::bad_request)?; + for document in body.into_inner() { + document_addition.update_document(document); + } - data.db.main_write(|w| index.main.put_schema(w, &schema))?; - } - - let mut document_addition = if is_partial { - index.documents_partial_addition() - } else { - index.documents_addition() - }; - - for document in body.into_inner() { - document_addition.update_document(document); - } - - let update_id = data.db.update_write(|w| document_addition.finalize(w))?; - - Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) + Ok(data.db.update_write(|w| document_addition.finalize(w))?) + })?; + return Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))); } #[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] @@ -243,7 +238,6 @@ async fn delete_documents( .open_index(&path.index_uid) .ok_or(Error::index_not_found(&path.index_uid))?; - let mut documents_deletion = index.documents_deletion(); for document_id in body.into_inner() { diff --git a/meilisearch-http/src/routes/setting.rs b/meilisearch-http/src/routes/setting.rs index 9e8bb8031..00562eed0 100644 --- a/meilisearch-http/src/routes/setting.rs +++ b/meilisearch-http/src/routes/setting.rs @@ -53,13 +53,12 @@ async fn update_all( path: web::Path, body: web::Json, ) -> Result { - let settings = body - .into_inner() - .to_update() - .map_err(Error::bad_request)?; - - let update_id = data.db.update_write::<_, _, Error>(|writer| { - update_all_settings_txn(&data, settings, &path.index_uid, writer) + let update_id = data.get_or_create_index(&path.index_uid, |index| { + Ok(data.db.update_write::<_, _, ResponseError>(|writer| { + let settings = body.into_inner().to_update().map_err(Error::bad_request)?; + let update_id = index.settings_update(writer, settings)?; + Ok(update_id) + })?) })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) @@ -71,11 +70,7 @@ pub fn get_all_sync(data: &web::Data, reader: &MainReader, index_uid: &str .open_index(index_uid) .ok_or(Error::index_not_found(index_uid))?; - let stop_words: BTreeSet = index - .main - .stop_words(reader)? - .into_iter() - .collect(); + let stop_words: BTreeSet = index.main.stop_words(&reader)?.into_iter().collect(); let synonyms_list = index.main.synonyms(reader)?; @@ -94,22 +89,19 @@ pub fn get_all_sync(data: &web::Data, reader: &MainReader, index_uid: &str .map(|r| r.to_string()) .collect(); - - let schema = index.main.schema(reader)?; + let schema = index.main.schema(&reader)?; let distinct_attribute = match (index.main.distinct_attribute(reader)?, &schema) { (Some(id), Some(schema)) => schema.name(id).map(str::to_string), _ => None, }; - let attributes_for_faceting = match (&schema, &index.main.attributes_for_faceting(reader)?) { - (Some(schema), Some(attrs)) => { - attrs - .iter() - .filter_map(|&id| schema.name(id)) - .map(str::to_string) - .collect() - } + let attributes_for_faceting = match (&schema, &index.main.attributes_for_faceting(&reader)?) { + (Some(schema), Some(attrs)) => attrs + .iter() + .filter_map(|&id| schema.name(id)) + .map(str::to_string) + .collect(), _ => vec![], }; @@ -159,7 +151,9 @@ async fn delete_all( attributes_for_faceting: UpdateState::Clear, }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -198,18 +192,17 @@ async fn update_rules( path: web::Path, body: web::Json>>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = Settings { + ranking_rules: Some(body.into_inner()), + ..Settings::default() + }; - let settings = Settings { - ranking_rules: Some(body.into_inner()), - ..Settings::default() - }; - - let settings = settings.to_update().map_err(Error::bad_request)?; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let settings = settings.to_update().map_err(Error::bad_request)?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -232,7 +225,9 @@ async fn delete_rules( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -269,18 +264,17 @@ async fn update_distinct( path: web::Path, body: web::Json>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = Settings { + distinct_attribute: Some(body.into_inner()), + ..Settings::default() + }; - let settings = Settings { - distinct_attribute: Some(body.into_inner()), - ..Settings::default() - }; - - let settings = settings.to_update().map_err(Error::bad_request)?; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let settings = settings.to_update().map_err(Error::bad_request)?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -303,7 +297,9 @@ async fn delete_distinct( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -322,8 +318,7 @@ async fn get_searchable( .ok_or(Error::index_not_found(&path.index_uid))?; let reader = data.db.main_read_txn()?; let schema = index.main.schema(&reader)?; - let searchable_attributes: Option> = - schema.as_ref().map(get_indexed_attributes); + let searchable_attributes: Option> = schema.as_ref().map(get_indexed_attributes); Ok(HttpResponse::Ok().json(searchable_attributes)) } @@ -337,19 +332,18 @@ async fn update_searchable( path: web::Path, body: web::Json>>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = Settings { + searchable_attributes: Some(body.into_inner()), + ..Settings::default() + }; - let settings = Settings { - searchable_attributes: Some(body.into_inner()), - ..Settings::default() - }; + let settings = settings.to_update().map_err(Error::bad_request)?; - let settings = settings.to_update().map_err(Error::bad_request)?; - - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -372,7 +366,9 @@ async fn delete_searchable( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -407,18 +403,17 @@ async fn update_displayed( path: web::Path, body: web::Json>>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = Settings { + displayed_attributes: Some(body.into_inner()), + ..Settings::default() + }; - let settings = Settings { - displayed_attributes: Some(body.into_inner()), - ..Settings::default() - }; - - let settings = settings.to_update().map_err(Error::bad_request)?; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let settings = settings.to_update().map_err(Error::bad_request)?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -441,7 +436,9 @@ async fn delete_displayed( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -459,20 +456,16 @@ async fn get_attributes_for_faceting( .open_index(&path.index_uid) .ok_or(Error::index_not_found(&path.index_uid))?; - let attributes_for_faceting = data - .db - .main_read::<_, _, ResponseError>(|reader| { + let attributes_for_faceting = data.db.main_read::<_, _, ResponseError>(|reader| { let schema = index.main.schema(reader)?; let attrs = index.main.attributes_for_faceting(reader)?; let attr_names = match (&schema, &attrs) { - (Some(schema), Some(attrs)) => { - attrs - .iter() - .filter_map(|&id| schema.name(id)) - .map(str::to_string) - .collect() - } - _ => vec![] + (Some(schema), Some(attrs)) => attrs + .iter() + .filter_map(|&id| schema.name(id)) + .map(str::to_string) + .collect(), + _ => vec![], }; Ok(attr_names) })?; @@ -489,18 +482,17 @@ async fn update_attributes_for_faceting( path: web::Path, body: web::Json>>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = Settings { + attributes_for_faceting: Some(body.into_inner()), + ..Settings::default() + }; - let settings = Settings { - attributes_for_faceting: Some(body.into_inner()), - ..Settings::default() - }; - - let settings = settings.to_update().map_err(Error::bad_request)?; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let settings = settings.to_update().map_err(Error::bad_request)?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -523,7 +515,9 @@ async fn delete_attributes_for_faceting( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -532,7 +526,8 @@ fn get_indexed_attributes(schema: &Schema) -> Vec { if schema.is_indexed_all() { ["*"].iter().map(|s| s.to_string()).collect() } else { - schema.indexed_name() + schema + .indexed_name() .iter() .map(|s| s.to_string()) .collect() @@ -543,7 +538,8 @@ fn get_displayed_attributes(schema: &Schema) -> BTreeSet { if schema.is_displayed_all() { ["*"].iter().map(|s| s.to_string()).collect() } else { - schema.displayed_name() + schema + .displayed_name() .iter() .map(|s| s.to_string()) .collect() diff --git a/meilisearch-http/src/routes/stop_words.rs b/meilisearch-http/src/routes/stop_words.rs index 858b91705..c757b4d14 100644 --- a/meilisearch-http/src/routes/stop_words.rs +++ b/meilisearch-http/src/routes/stop_words.rs @@ -39,17 +39,16 @@ async fn update( path: web::Path, body: web::Json>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = SettingsUpdate { + stop_words: UpdateState::Update(body.into_inner()), + ..SettingsUpdate::default() + }; - let settings = SettingsUpdate { - stop_words: UpdateState::Update(body.into_inner()), - ..SettingsUpdate::default() - }; - - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -72,7 +71,9 @@ async fn delete( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } diff --git a/meilisearch-http/src/routes/synonym.rs b/meilisearch-http/src/routes/synonym.rs index fd0488534..5aefaaca5 100644 --- a/meilisearch-http/src/routes/synonym.rs +++ b/meilisearch-http/src/routes/synonym.rs @@ -50,17 +50,16 @@ async fn update( path: web::Path, body: web::Json>>, ) -> Result { - let index = data - .db - .open_index(&path.index_uid) - .ok_or(Error::index_not_found(&path.index_uid))?; + let update_id = data.get_or_create_index(&path.index_uid, |index| { + let settings = SettingsUpdate { + synonyms: UpdateState::Update(body.into_inner()), + ..SettingsUpdate::default() + }; - let settings = SettingsUpdate { - synonyms: UpdateState::Update(body.into_inner()), - ..SettingsUpdate::default() - }; - - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + Ok(data + .db + .update_write(|w| index.settings_update(w, settings))?) + })?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } @@ -83,7 +82,9 @@ async fn delete( ..SettingsUpdate::default() }; - let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; + let update_id = data + .db + .update_write(|w| index.settings_update(w, settings))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } diff --git a/meilisearch-http/tests/common.rs b/meilisearch-http/tests/common.rs index da2bc39ad..0cdd3b618 100644 --- a/meilisearch-http/tests/common.rs +++ b/meilisearch-http/tests/common.rs @@ -15,15 +15,24 @@ use meilisearch_http::option::Opt; #[macro_export] macro_rules! test_post_get_search { ($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => { - let post_query: meilisearch_http::routes::search::SearchQueryPost = serde_json::from_str(&$query.clone().to_string()).unwrap(); + let post_query: meilisearch_http::routes::search::SearchQueryPost = + serde_json::from_str(&$query.clone().to_string()).unwrap(); let get_query: meilisearch_http::routes::search::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())); + 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())); + let _ = ::std::panic::catch_unwind(|| $block).map_err(|e| { + panic!( + "panic in post route: {:?}", + e.downcast_ref::<&str>().unwrap() + ) + }); }; } @@ -61,7 +70,6 @@ impl Server { } pub async fn test_server() -> Self { - let mut server = Self::with_uid("test"); let body = json!({ @@ -151,7 +159,8 @@ impl Server { pub async fn get_request(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("get_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; + let mut app = + test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::get().uri(url).to_request(); let res = test::call_service(&mut app, req).await; @@ -165,7 +174,8 @@ impl Server { pub async fn post_request(&self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("post_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; + let mut app = + test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::post() .uri(url) @@ -183,8 +193,7 @@ impl Server { eprintln!("post_request_async: {}", url); let (response, status_code) = self.post_request(url, body).await; - // eprintln!("response: {}", response); - assert_eq!(status_code, 202); + eprintln!("response: {}", response); assert!(response["updateId"].as_u64().is_some()); self.wait_update_id(response["updateId"].as_u64().unwrap()) .await; @@ -194,7 +203,8 @@ impl Server { pub async fn put_request(&mut self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("put_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; + let mut app = + test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::put() .uri(url) @@ -222,7 +232,8 @@ impl Server { pub async fn delete_request(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("delete_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; + let mut app = + test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::delete().uri(url).to_request(); let res = test::call_service(&mut app, req).await; @@ -340,9 +351,9 @@ impl Server { self.delete_request_async(&url).await } - pub async fn delete_multiple_documents(&mut self, body: Value) { + pub async fn delete_multiple_documents(&mut self, body: Value) -> (Value, StatusCode) { let url = format!("/indexes/{}/documents/delete-batch", self.uid); - self.post_request_async(&url, body).await; + self.post_request_async(&url, body).await } pub async fn get_all_settings(&mut self) -> (Value, StatusCode) { @@ -355,6 +366,11 @@ impl Server { self.post_request_async(&url, body).await; } + pub async fn update_all_settings_sync(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings", self.uid); + self.post_request(&url, body).await + } + pub async fn delete_all_settings(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings", self.uid); self.delete_request_async(&url).await @@ -390,6 +406,11 @@ impl Server { self.post_request_async(&url, body).await; } + pub async fn update_distinct_attribute_sync(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/distinct-attribute", self.uid); + self.post_request(&url, body).await + } + pub async fn delete_distinct_attribute(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/distinct-attribute", self.uid); self.delete_request_async(&url).await @@ -410,6 +431,11 @@ impl Server { self.post_request_async(&url, body).await; } + pub async fn update_searchable_attributes_sync(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/searchable-attributes", self.uid); + self.post_request(&url, body).await + } + pub async fn delete_searchable_attributes(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/searchable-attributes", self.uid); self.delete_request_async(&url).await @@ -425,11 +451,39 @@ impl Server { self.post_request_async(&url, body).await; } + pub async fn update_displayed_attributes_sync(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/displayed-attributes", self.uid); + self.post_request(&url, body).await + } + pub async fn delete_displayed_attributes(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/displayed-attributes", self.uid); self.delete_request_async(&url).await } + pub async fn get_attributes_for_faceting(&mut self) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid); + self.get_request(&url).await + } + + pub async fn update_attributes_for_faceting(&mut self, body: Value) { + let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid); + self.post_request_async(&url, body).await; + } + + pub async fn update_attributes_for_faceting_sync( + &mut self, + body: Value, + ) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid); + self.post_request(&url, body).await + } + + pub async fn delete_attributes_for_faceting(&mut self) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid); + self.delete_request_async(&url).await + } + pub async fn get_synonyms(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/synonyms", self.uid); self.get_request(&url).await @@ -440,6 +494,11 @@ impl Server { self.post_request_async(&url, body).await; } + pub async fn update_synonyms_sync(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/synonyms", self.uid); + self.post_request(&url, body).await + } + pub async fn delete_synonyms(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/synonyms", self.uid); self.delete_request_async(&url).await @@ -455,6 +514,11 @@ impl Server { self.post_request_async(&url, body).await; } + pub async fn update_stop_words_sync(&mut self, body: Value) -> (Value, StatusCode) { + let url = format!("/indexes/{}/settings/stop-words", self.uid); + self.post_request(&url, body).await + } + pub async fn delete_stop_words(&mut self) -> (Value, StatusCode) { let url = format!("/indexes/{}/settings/stop-words", self.uid); self.delete_request_async(&url).await diff --git a/meilisearch-http/tests/documents_add.rs b/meilisearch-http/tests/documents_add.rs index fae29f15e..382a1ed43 100644 --- a/meilisearch-http/tests/documents_add.rs +++ b/meilisearch-http/tests/documents_add.rs @@ -192,7 +192,9 @@ async fn add_document_with_long_field() { "url":"/configuration/app/web.html#locations" }]); server.add_or_replace_multiple_documents(body).await; - let (response, _status) = server.search_post(json!({ "q": "request_buffering" })).await; + let (response, _status) = server + .search_post(json!({ "q": "request_buffering" })) + .await; assert!(!response["hits"].as_array().unwrap().is_empty()); } @@ -213,5 +215,8 @@ async fn documents_with_same_id_are_overwritten() { server.add_or_replace_multiple_documents(documents).await; let (response, _status) = server.get_all_documents().await; assert_eq!(response.as_array().unwrap().len(), 1); - assert_eq!(response.as_array().unwrap()[0].as_object().unwrap()["content"], "test2"); + assert_eq!( + response.as_array().unwrap()[0].as_object().unwrap()["content"], + "test2" + ); } diff --git a/meilisearch-http/tests/lazy_index_creation.rs b/meilisearch-http/tests/lazy_index_creation.rs new file mode 100644 index 000000000..6730db82e --- /dev/null +++ b/meilisearch-http/tests/lazy_index_creation.rs @@ -0,0 +1,446 @@ +use serde_json::json; + +mod common; + +#[actix_rt::test] +async fn create_index_lazy_by_pushing_documents() { + let mut server = common::Server::with_uid("movies"); + + // 1 - Add documents + + let body = json!([{ + "title": "Test", + "comment": "comment test" + }]); + + let url = "/indexes/movies/documents?primaryKey=title"; + let (response, status_code) = server.post_request(&url, body).await; + assert_eq!(status_code, 202); + let update_id = response["updateId"].as_u64().unwrap(); + server.wait_update_id(update_id).await; + + // 3 - Check update success + + let (response, status_code) = server.get_update_status(update_id).await; + assert_eq!(status_code, 200); + assert_eq!(response["status"], "processed"); +} + +#[actix_rt::test] +async fn create_index_lazy_by_pushing_documents_and_discover_pk() { + let mut server = common::Server::with_uid("movies"); + + // 1 - Add documents + + let body = json!([{ + "id": 1, + "title": "Test", + "comment": "comment test" + }]); + + let url = "/indexes/movies/documents"; + let (response, status_code) = server.post_request(&url, body).await; + assert_eq!(status_code, 202); + let update_id = response["updateId"].as_u64().unwrap(); + server.wait_update_id(update_id).await; + + // 3 - Check update success + + let (response, status_code) = server.get_update_status(update_id).await; + assert_eq!(status_code, 200); + assert_eq!(response["status"], "processed"); +} + +#[actix_rt::test] +async fn create_index_lazy_by_pushing_documents_with_wrong_name() { + let server = common::Server::with_uid("wrong&name"); + + let body = json!([{ + "title": "Test", + "comment": "comment test" + }]); + + let url = "/indexes/wrong&name/documents?primaryKey=title"; + let (response, status_code) = server.post_request(&url, body).await; + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_index_uid"); +} + +#[actix_rt::test] +async fn create_index_lazy_add_documents_failed() { + let mut server = common::Server::with_uid("wrong&name"); + + let body = json!([{ + "title": "Test", + "comment": "comment test" + }]); + + let url = "/indexes/wrong&name/documents"; + let (response, status_code) = server.post_request(&url, body).await; + assert_eq!(status_code, 400); + assert_eq!(response["errorCode"], "invalid_index_uid"); + + let (_, status_code) = server.get_index().await; + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_settings() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!({ + "rankingRules": [ + "typo", + "words", + "proximity", + "attribute", + "wordsPosition", + "exactness", + "desc(registered)", + "desc(age)", + ], + "distinctAttribute": "id", + "searchableAttributes": [ + "id", + "name", + "color", + "gender", + "email", + "phone", + "address", + "registered", + "about" + ], + "displayedAttributes": [ + "name", + "gender", + "email", + "registered", + "age", + ], + "stopWords": [ + "ad", + "in", + "ut", + ], + "synonyms": { + "road": ["street", "avenue"], + "street": ["avenue"], + }, + "attributesForFaceting": ["name"], + }); + + server.update_all_settings(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_settings_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!({ + "rankingRules": [ + "other", + "words", + "proximity", + "attribute", + "wordsPosition", + "exactness", + "desc(registered)", + "desc(age)", + ], + "distinctAttribute": "id", + "searchableAttributes": [ + "id", + "name", + "color", + "gender", + "email", + "phone", + "address", + "registered", + "about" + ], + "displayedAttributes": [ + "name", + "gender", + "email", + "registered", + "age", + ], + "stopWords": [ + "ad", + "in", + "ut", + ], + "synonyms": { + "road": ["street", "avenue"], + "street": ["avenue"], + }, + "anotherSettings": ["name"], + }); + + let (_, status_code) = server.update_all_settings_sync(body.clone()).await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_ranking_rules() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!([ + "typo", + "words", + "proximity", + "attribute", + "wordsPosition", + "exactness", + "desc(registered)", + "desc(age)", + ]); + + server.update_ranking_rules(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_ranking_rules_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!({ + "rankingRules": 123, + }); + + let (_, status_code) = server.update_ranking_rules_sync(body.clone()).await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_distinct_attribute() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!("type"); + + server.update_distinct_attribute(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_distinct_attribute_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(123); + + let (resp, status_code) = server.update_distinct_attribute_sync(body.clone()).await; + eprintln!("resp: {:?}", resp); + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (resp, status_code) = server.get_all_settings().await; + eprintln!("resp: {:?}", resp); + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_searchable_attributes() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(["title", "description"]); + + server.update_searchable_attributes(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_searchable_attributes_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(123); + + let (_, status_code) = server.update_searchable_attributes_sync(body.clone()).await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_displayed_attributes() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(["title", "description"]); + + server.update_displayed_attributes(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_displayed_attributes_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(123); + + let (_, status_code) = server.update_displayed_attributes_sync(body.clone()).await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_attributes_for_faceting() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(["title", "description"]); + + server.update_attributes_for_faceting(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_attributes_for_faceting_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(123); + + let (_, status_code) = server + .update_attributes_for_faceting_sync(body.clone()) + .await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_synonyms() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!({ + "road": ["street", "avenue"], + "street": ["avenue"], + }); + + server.update_synonyms(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_synonyms_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(123); + + let (_, status_code) = server.update_synonyms_sync(body.clone()).await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_stop_words() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(["le", "la", "les"]); + + server.update_stop_words(body.clone()).await; + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 200); +} + +#[actix_rt::test] +async fn create_index_lazy_by_sending_stop_words_with_error() { + let mut server = common::Server::with_uid("movies"); + // 2 - Send the settings + + let body = json!(123); + + let (_, status_code) = server.update_stop_words_sync(body.clone()).await; + assert_eq!(status_code, 400); + + // 3 - Get all settings and compare to the previous one + + let (_, status_code) = server.get_all_settings().await; + + assert_eq!(status_code, 404); +}