diff --git a/meilisearch-core/src/settings.rs b/meilisearch-core/src/settings.rs index d3f2bf5f9..0759524f5 100644 --- a/meilisearch-core/src/settings.rs +++ b/meilisearch-core/src/settings.rs @@ -2,7 +2,7 @@ use std::sync::Mutex; use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::str::FromStr; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use once_cell::sync::Lazy; static RANKING_RULE_REGEX: Lazy> = Lazy::new(|| { @@ -12,6 +12,7 @@ static RANKING_RULE_REGEX: Lazy> = Lazy::new(|| { #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Settings { pub ranking_rules: Option>, pub ranking_distinct: Option, @@ -22,8 +23,8 @@ pub struct Settings { pub synonyms: Option>>, } -impl Into for Settings { - fn into(self) -> SettingsUpdate { +impl Settings { + pub fn into_cleared(self) -> SettingsUpdate { let settings = self.clone(); let ranking_rules = match settings.ranking_rules { @@ -32,42 +33,23 @@ impl Into for Settings { }; SettingsUpdate { - ranking_rules: ranking_rules.into(), - ranking_distinct: settings.ranking_distinct.into(), - attribute_identifier: settings.attribute_identifier.into(), - attributes_searchable: settings.attributes_searchable.into(), - attributes_displayed: settings.attributes_displayed.into(), - stop_words: settings.stop_words.into(), - synonyms: settings.synonyms.into(), + ranking_rules: UpdateState::convert_with_default(ranking_rules, UpdateState::Clear), + ranking_distinct: UpdateState::convert_with_default(settings.ranking_distinct, UpdateState::Clear), + attribute_identifier: UpdateState::convert_with_default(settings.attribute_identifier, UpdateState::Clear), + attributes_searchable: UpdateState::convert_with_default(settings.attributes_searchable, UpdateState::Clear), + attributes_displayed: UpdateState::convert_with_default(settings.attributes_displayed, UpdateState::Clear), + stop_words: UpdateState::convert_with_default(settings.stop_words, UpdateState::Clear), + synonyms: UpdateState::convert_with_default(settings.synonyms, UpdateState::Clear), } } } -#[derive(Default, Clone, Serialize, Deserialize)] -pub struct SettingsComplete { - #[serde(default, deserialize_with = "deserialize_some")] - pub ranking_rules: Option>>, - #[serde(default, deserialize_with = "deserialize_some")] - pub ranking_distinct: Option>, - #[serde(default, deserialize_with = "deserialize_some")] - pub attribute_identifier: Option>, - #[serde(default, deserialize_with = "deserialize_some")] - pub attributes_searchable: Option>>, - #[serde(default, deserialize_with = "deserialize_some")] - pub attributes_displayed: Option>>, - #[serde(default, deserialize_with = "deserialize_some")] - pub stop_words: Option>>, - #[serde(default, deserialize_with = "deserialize_some")] - pub synonyms: Option>>>, -} - -impl Into for SettingsComplete { +impl Into for Settings { fn into(self) -> SettingsUpdate { let settings = self.clone(); let ranking_rules = match settings.ranking_rules { - Some(Some(rules)) => Some(Some(RankingRule::from_vec(rules))), - Some(None) => Some(None), + Some(rules) => Some(RankingRule::from_vec(rules)), None => None, }; @@ -101,16 +83,6 @@ impl From> for UpdateState { } } -impl From>> for UpdateState { - fn from(opt: Option>) -> UpdateState { - match opt { - Some(Some(t)) => UpdateState::Update(t), - Some(None) => UpdateState::Clear, - None => UpdateState::Nothing, - } - } -} - impl UpdateState { pub fn is_changed(&self) -> bool { match self { @@ -118,6 +90,13 @@ impl UpdateState { _ => true, } } + + fn convert_with_default(opt: Option, default: UpdateState) -> UpdateState { + match opt { + Some(t) => UpdateState::Update(t), + None => default, + } + } } #[derive(Debug, Clone)] @@ -169,9 +148,9 @@ impl FromStr for RankingRule { "_exact" => RankingRule::Exact, _ => { let captures = RANKING_RULE_REGEX.lock().unwrap().captures(s).unwrap(); - match captures[0].as_ref() { - "asc" => RankingRule::Asc(captures[1].to_string()), - "dsc" => RankingRule::Dsc(captures[1].to_string()), + match captures[1].as_ref() { + "asc" => RankingRule::Asc(captures[2].to_string()), + "dsc" => RankingRule::Dsc(captures[2].to_string()), _ => return Err(RankingRuleConversionError) } } @@ -220,11 +199,3 @@ impl Default for SettingsUpdate { } } } - -// Any value that is present is considered Some value, including null. -fn deserialize_some<'de, T, D>(deserializer: D) -> Result, D::Error> - where T: Deserialize<'de>, - D: Deserializer<'de> -{ - Deserialize::deserialize(deserializer).map(Some) -} diff --git a/meilisearch-http/src/helpers/tide.rs b/meilisearch-http/src/helpers/tide.rs index b12d05152..f2da620c3 100644 --- a/meilisearch-http/src/helpers/tide.rs +++ b/meilisearch-http/src/helpers/tide.rs @@ -8,10 +8,10 @@ use tide::Request; pub trait RequestExt { fn is_allowed(&self, acl: ACL) -> SResult<()>; - fn header(&self, name: &str) -> Result; - fn url_param(&self, name: &str) -> Result; - fn index(&self) -> Result; - fn identifier(&self) -> Result; + fn header(&self, name: &str) -> SResult; + fn url_param(&self, name: &str) -> SResult; + fn index(&self) -> SResult; + fn identifier(&self) -> SResult; } impl RequestExt for Request { @@ -73,7 +73,7 @@ impl RequestExt for Request { Ok(()) } - fn header(&self, name: &str) -> Result { + fn header(&self, name: &str) -> SResult { let header = self .headers() .get(name) @@ -84,14 +84,13 @@ impl RequestExt for Request { Ok(header) } - fn url_param(&self, name: &str) -> Result { - let param = self - .param::(name) - .map_err(|e| ResponseError::bad_parameter(name, e))?; + fn url_param(&self, name: &str) -> SResult { + let param = self.param::(name) + .map_err(|_| ResponseError::bad_parameter("identifier", ""))?; Ok(param) } - fn index(&self) -> Result { + fn index(&self) -> SResult { let index_uid = self.url_param("index")?; let index = self .state() @@ -101,10 +100,9 @@ impl RequestExt for Request { Ok(index) } - fn identifier(&self) -> Result { - let name = self - .param::("identifier") - .map_err(|e| ResponseError::bad_parameter("identifier", e))?; + fn identifier(&self) -> SResult { + let name = self.param::("identifier") + .map_err(|_| ResponseError::bad_parameter("identifier", ""))?; Ok(name) } diff --git a/meilisearch-http/src/routes/setting.rs b/meilisearch-http/src/routes/setting.rs index b001b8fc9..3aad5a282 100644 --- a/meilisearch-http/src/routes/setting.rs +++ b/meilisearch-http/src/routes/setting.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; use serde::{Deserialize, Serialize}; use tide::{Request, Response}; -use meilisearch_core::settings::{SettingsUpdate, UpdateState, Settings, SettingsComplete}; +use meilisearch_core::settings::{SettingsUpdate, UpdateState, Settings}; use crate::error::{ResponseError, SResult}; use crate::helpers::tide::RequestExt; @@ -77,11 +77,11 @@ pub async fn get_all(ctx: Request) -> SResult { pub async fn update_all(mut ctx: Request) -> SResult { ctx.is_allowed(SettingsWrite)?; let index = ctx.index()?; - let settings: SettingsComplete = ctx.body_json().await.map_err(ResponseError::bad_request)?; + let settings: Settings = ctx.body_json().await.map_err(ResponseError::bad_request)?; let db = &ctx.state().db; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; @@ -113,6 +113,7 @@ pub async fn delete_all(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct GetRankingSettings { pub ranking_rules: Option>, pub ranking_distinct: Option, @@ -141,9 +142,10 @@ pub async fn get_ranking(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SetRankingSettings { - pub ranking_rules: Option>>, - pub ranking_distinct: Option>, + pub ranking_rules: Option>, + pub ranking_distinct: Option, } pub async fn update_ranking(mut ctx: Request) -> SResult { @@ -152,14 +154,14 @@ pub async fn update_ranking(mut ctx: Request) -> SResult { let settings: SetRankingSettings = ctx.body_json().await.map_err(ResponseError::bad_request)?; let db = &ctx.state().db; - let settings = SettingsComplete { + let settings = Settings { ranking_rules: settings.ranking_rules, ranking_distinct: settings.ranking_distinct, - .. SettingsComplete::default() + .. Settings::default() }; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; @@ -187,6 +189,7 @@ pub async fn delete_ranking(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct GetRankingRulesSettings { pub ranking_rules: Option>, } @@ -212,8 +215,9 @@ pub async fn get_rules(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SetRankingRulesSettings { - pub ranking_rules: Option>>, + pub ranking_rules: Option>, } pub async fn update_rules(mut ctx: Request) -> SResult { @@ -223,13 +227,13 @@ pub async fn update_rules(mut ctx: Request) -> SResult { .map_err(ResponseError::bad_request)?; let db = &ctx.state().db; - let settings = SettingsComplete { + let settings = Settings { ranking_rules: settings.ranking_rules, - .. SettingsComplete::default() + .. Settings::default() }; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; @@ -256,6 +260,7 @@ pub async fn delete_rules(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct GetRankingDistinctSettings { pub ranking_distinct: Option, } @@ -275,8 +280,9 @@ pub async fn get_distinct(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SetRankingDistinctSettings { - pub ranking_distinct: Option>, + pub ranking_distinct: Option, } pub async fn update_distinct(mut ctx: Request) -> SResult { @@ -286,13 +292,13 @@ pub async fn update_distinct(mut ctx: Request) -> SResult { .map_err(ResponseError::bad_request)?; let db = &ctx.state().db; - let settings = SettingsComplete { + let settings = Settings { ranking_distinct: settings.ranking_distinct, - .. SettingsComplete::default() + .. Settings::default() }; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; @@ -319,6 +325,7 @@ pub async fn delete_distinct(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct GetAttributesSettings { pub attribute_identifier: Option, pub attributes_searchable: Option>, @@ -347,10 +354,11 @@ pub async fn get_attributes(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SetAttributesSettings { - pub attribute_identifier: Option>, - pub attributes_searchable: Option>>, - pub attributes_displayed: Option>>, + pub attribute_identifier: Option, + pub attributes_searchable: Option>, + pub attributes_displayed: Option>, } pub async fn update_attributes(mut ctx: Request) -> SResult { @@ -360,15 +368,15 @@ pub async fn update_attributes(mut ctx: Request) -> SResult { .map_err(ResponseError::bad_request)?; let db = &ctx.state().db; - let settings = SettingsComplete { + let settings = Settings { attribute_identifier: settings.attribute_identifier, attributes_searchable: settings.attributes_searchable, attributes_displayed: settings.attributes_displayed, - .. SettingsComplete::default() + .. Settings::default() }; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; @@ -395,6 +403,7 @@ pub async fn delete_attributes(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AttributesIdentifierSettings { pub attribute_identifier: Option, } @@ -417,6 +426,7 @@ pub async fn get_identifier(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct GetAttributesSearchableSettings { pub attributes_searchable: Option>, } @@ -439,8 +449,9 @@ pub async fn get_searchable(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SetAttributesSearchableSettings { - pub attributes_searchable: Option>>, + pub attributes_searchable: Option>, } pub async fn update_searchable(mut ctx: Request) -> SResult { @@ -450,13 +461,13 @@ pub async fn update_searchable(mut ctx: Request) -> SResult { .map_err(ResponseError::bad_request)?; let db = &ctx.state().db; - let settings = SettingsComplete { + let settings = Settings { attributes_searchable: settings.attributes_searchable, - .. SettingsComplete::default() + .. Settings::default() }; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; @@ -482,6 +493,7 @@ pub async fn delete_searchable(ctx: Request) -> SResult { } #[derive(Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AttributesDisplayedSettings { pub attributes_displayed: Option>, } @@ -516,7 +528,7 @@ pub async fn update_displayed(mut ctx: Request) -> SResult { }; let mut writer = db.update_write_txn()?; - let update_id = index.settings_update(&mut writer, settings.into())?; + let update_id = index.settings_update(&mut writer, settings.into_cleared())?; writer.commit()?; let response_body = IndexUpdateResponse { update_id }; diff --git a/meilisearch-http/tests/settings.rs b/meilisearch-http/tests/settings.rs index 095220813..eeb379752 100644 --- a/meilisearch-http/tests/settings.rs +++ b/meilisearch-http/tests/settings.rs @@ -27,7 +27,7 @@ fn write_all_and_delete() { // 2 - Send the settings let json = json!({ - "ranking_rules": [ + "rankingRules": [ "_typo", "_words", "_proximity", @@ -37,9 +37,9 @@ fn write_all_and_delete() { "dsc(release_date)", "dsc(rank)", ], - "ranking_distinct": "movie_id", - "attribute_identifier": "uid", - "attributes_searchable": [ + "rankingDistinct": "movie_id", + "attributeIdentifier": "uid", + "attributesSearchable": [ "uid", "movie_id", "title", @@ -48,14 +48,14 @@ fn write_all_and_delete() { "release_date", "rank", ], - "attributes_displayed": [ + "attributesDisplayed": [ "title", "description", "poster", "release_date", "rank", ], - "stop_words": [ + "stopWords": [ "the", "a", "an", @@ -105,12 +105,12 @@ fn write_all_and_delete() { let res_value: Value = serde_json::from_slice(&buf).unwrap(); let json = json!({ - "ranking_rules": null, - "ranking_distinct": null, - "attribute_identifier": null, - "attributes_searchable": null, - "attributes_displayed": null, - "stop_words": null, + "rankingRules": null, + "rankingDistinct": null, + "attributeIdentifier": null, + "attributesSearchable": null, + "attributesDisplayed": null, + "stopWords": null, "synonyms": null, }); @@ -135,7 +135,7 @@ fn write_all_and_update() { // 2 - Send the settings let json = json!({ - "ranking_rules": [ + "rankingRules": [ "_typo", "_words", "_proximity", @@ -145,9 +145,9 @@ fn write_all_and_update() { "dsc(release_date)", "dsc(rank)", ], - "ranking_distinct": "movie_id", - "attribute_identifier": "uid", - "attributes_searchable": [ + "rankingDistinct": "movie_id", + "attributeIdentifier": "uid", + "attributesSearchable": [ "uid", "movie_id", "title", @@ -156,14 +156,14 @@ fn write_all_and_update() { "release_date", "rank", ], - "attributes_displayed": [ + "attributesDisplayed": [ "title", "description", "poster", "release_date", "rank", ], - "stop_words": [ + "stopWords": [ "the", "a", "an", @@ -197,7 +197,7 @@ fn write_all_and_update() { // 4 - Update all settings let json_update = json!({ - "ranking_rules": [ + "rankingRules": [ "_typo", "_words", "_proximity", @@ -206,21 +206,20 @@ fn write_all_and_update() { "_exact", "dsc(release_date)", ], - "ranking_distinct": null, - "attribute_identifier": "uid", - "attributes_searchable": [ + "attributeIdentifier": "uid", + "attributesSearchable": [ "title", "description", "uid", ], - "attributes_displayed": [ + "attributesDisplayed": [ "title", "description", "release_date", "rank", "poster", ], - "stop_words": [ + "stopWords": [ ], "synonyms": { "wolverine": ["xmen", "logan"], @@ -247,7 +246,7 @@ fn write_all_and_update() { let res_value: Value = serde_json::from_slice(&buf).unwrap(); let res_expected = json!({ - "ranking_rules": [ + "rankingRules": [ "_typo", "_words", "_proximity", @@ -256,27 +255,26 @@ fn write_all_and_update() { "_exact", "dsc(release_date)", ], - "ranking_distinct": "movie_id", - "attribute_identifier": "uid", - "attributes_searchable": [ + "rankingDistinct": null, + "attributeIdentifier": "uid", + "attributesSearchable": [ "title", "description", "uid", ], - "attributes_displayed": [ + "attributesDisplayed": [ "title", "description", "release_date", "rank", "poster", ], - "stop_words": [ - ], + "stopWords": null, "synonyms": { "wolverine": ["xmen", "logan"], "logan": ["wolverine", "xmen"], } }); - assert_json_eq!(json_update, res_value, ordered: false); + assert_json_eq!(res_expected, res_value, ordered: false); }