diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index cfa10a2d..6390462c 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -1650,7 +1650,7 @@ get_experimental_features_1: |- update_experimental_features_1: |- let client = Client::new("http://localhost:7700", Some("apiKey")); let features = ExperimentalFeatures::new(&client); - // update the feature you want here + features.set_metrics(true) let res = features .update() .await @@ -1770,3 +1770,23 @@ reset_localized_attribute_settings_1: |- .reset_localized_attributes() .await .unwrap(); +get_network_1: |- + let network: Network = client.get_network().await.unwrap(); +update_network_1: |- + client.update_network( + NetworkUpdate::new() + .with_self("ms-00") + .with_remotes(&[ + Remote { + name: "ms-00".to_string(), + url: "http://INSTANCE_URL".to_string(), + search_api_key: Some("INSTANCE_API_KEY".to_string()), + }, + Remote { + name: "ms-01".to_string(), + url: "http://ANOTHER_INSTANCE_URL".to_string(), + search_api_key: Some("ANOTHER_INSTANCE_API_KEY".to_string()), + }, + ]), + ) + .await.unwrap(); diff --git a/src/client.rs b/src/client.rs index 691f2f08..a4fc8d43 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,6 +8,7 @@ use crate::{ errors::*, indexes::*, key::{Key, KeyBuilder, KeyUpdater, KeysQuery, KeysResults}, + network::{Network, NetworkUpdate}, request::*, search::*, task_info::TaskInfo, @@ -1076,6 +1077,76 @@ impl Client { Ok(tasks) } + /// Get the network configuration (sharding). + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, features::ExperimentalFeatures}; + /// # + /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { + /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap(); + /// # ExperimentalFeatures::new(&client).set_network(true).update().await.unwrap(); + /// let network = client.get_network().await.unwrap(); + /// # }); + /// ``` + pub async fn get_network(&self) -> Result { + let network = self + .http_client + .request::<(), (), Network>( + &format!("{}/network", self.host), + Method::Get { query: () }, + 200, + ) + .await?; + + Ok(network) + } + + /// Update the network configuration (sharding). + /// + /// # Example + /// + /// ``` + /// # use meilisearch_sdk::{client::*, features::ExperimentalFeatures, network::*}; + /// # + /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700"); + /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey"); + /// # + /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { + /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap(); + /// # ExperimentalFeatures::new(&client).set_network(true).update().await.unwrap(); + /// let network = client.update_network( + /// NetworkUpdate::new() + /// // .reset_self() + /// // .reset_remotes() + /// // .delete_remotes(&["ms-00"]) + /// .with_self("ms-00") + /// .with_remotes(&[Remote { + /// name: "ms-00".to_string(), + /// url: "http://localhost:7700".to_string(), + /// search_api_key: Some("secret".to_string()), + /// }]), + /// ) + /// .await.unwrap(); + /// # }); + /// ``` + pub async fn update_network(&self, network_update: &NetworkUpdate) -> Result { + self.http_client + .request::<(), &NetworkUpdate, Network>( + &format!("{}/network", self.host), + Method::Patch { + body: network_update, + query: (), + }, + 200, + ) + .await + } + /// Generates a new tenant token. /// /// # Example @@ -1166,7 +1237,10 @@ mod tests { use meilisearch_test_macro::meilisearch_test; - use crate::{client::*, key::Action, reqwest::qualified_version}; + use crate::{ + client::*, features::ExperimentalFeatures, key::Action, network::Remote, + reqwest::qualified_version, + }; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Document { @@ -1316,6 +1390,80 @@ mod tests { assert_eq!(tasks.limit, 20); } + async fn enable_network(client: &Client) { + ExperimentalFeatures::new(client) + .set_network(true) + .update() + .await + .unwrap(); + } + + #[meilisearch_test] + async fn test_get_network(client: Client) { + enable_network(&client).await; + + let network = client.get_network().await.unwrap(); + assert!(matches!( + network, + Network { + self_: _, + remotes: _, + } + )) + } + + #[meilisearch_test] + async fn test_update_network_self(client: Client) { + enable_network(&client).await; + + client + .update_network(NetworkUpdate::new().reset_self()) + .await + .unwrap(); + + let network = client + .update_network(NetworkUpdate::new().with_self("ms-00")) + .await + .unwrap(); + + assert_eq!(network.self_, Some("ms-00".to_string())); + } + + #[meilisearch_test] + async fn test_update_network_remotes(client: Client) { + enable_network(&client).await; + + client + .update_network(NetworkUpdate::new().reset_remotes()) + .await + .unwrap(); + + let network = client + .update_network(NetworkUpdate::new().with_remotes(&[Remote { + name: "ms-00".to_string(), + url: "http://localhost:7700".to_string(), + search_api_key: Some("secret".to_string()), + }])) + .await + .unwrap(); + + assert_eq!( + network.remotes, + vec![Remote { + name: "ms-00".to_string(), + url: "http://localhost:7700".to_string(), + search_api_key: Some("secret".to_string()), + }] + ); + + let network = client + .update_network(NetworkUpdate::new().delete_remotes(&["ms-00"])) + .await + .unwrap(); + + assert_eq!(network.remotes, vec![]); + } + #[meilisearch_test] async fn test_get_keys(client: Client) { let keys = client.get_keys().await.unwrap(); diff --git a/src/features.rs b/src/features.rs index dd7a5aef..26e8ca0b 100644 --- a/src/features.rs +++ b/src/features.rs @@ -8,7 +8,13 @@ use serde::{Deserialize, Serialize}; /// Struct representing the experimental features result from the API. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExperimentalFeaturesResult {} +pub struct ExperimentalFeaturesResult { + pub metrics: bool, + pub logs_route: bool, + pub contains_filter: bool, + pub network: bool, + pub edit_documents_by_function: bool, +} /// Struct representing the experimental features request. /// @@ -28,12 +34,30 @@ pub struct ExperimentalFeaturesResult {} pub struct ExperimentalFeatures<'a, Http: HttpClient> { #[serde(skip_serializing)] client: &'a Client, + + #[serde(skip_serializing_if = "Option::is_none")] + pub metrics: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub contains_filter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub logs_route: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub network: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub edit_documents_by_function: Option, } impl<'a, Http: HttpClient> ExperimentalFeatures<'a, Http> { #[must_use] pub fn new(client: &'a Client) -> Self { - ExperimentalFeatures { client } + ExperimentalFeatures { + client, + metrics: None, + logs_route: None, + network: None, + contains_filter: None, + edit_documents_by_function: None, + } } /// Get all the experimental features @@ -88,6 +112,34 @@ impl<'a, Http: HttpClient> ExperimentalFeatures<'a, Http> { ) .await } + + pub fn set_metrics(&mut self, metrics: bool) -> &mut Self { + self.metrics = Some(metrics); + self + } + + pub fn set_logs_route(&mut self, logs_route: bool) -> &mut Self { + self.logs_route = Some(logs_route); + self + } + + pub fn set_contains_filter(&mut self, contains_filter: bool) -> &mut Self { + self.contains_filter = Some(contains_filter); + self + } + + pub fn set_edit_documents_by_function( + &mut self, + edit_documents_by_function: bool, + ) -> &mut Self { + self.edit_documents_by_function = Some(edit_documents_by_function); + self + } + + pub fn set_network(&mut self, network: bool) -> &mut Self { + self.network = Some(network); + self + } } #[cfg(test)] @@ -96,12 +148,52 @@ mod tests { use meilisearch_test_macro::meilisearch_test; #[meilisearch_test] - async fn test_experimental_features_get(client: Client) { - let features = ExperimentalFeatures::new(&client); - // set feature here, once some exist again + async fn test_experimental_features_set_metrics(client: Client) { + let mut features = ExperimentalFeatures::new(&client); + features.set_metrics(true); + let _ = features.update().await.unwrap(); + + let res = features.get().await.unwrap(); + assert!(res.metrics) + } + + #[meilisearch_test] + async fn test_experimental_features_set_logs_route(client: Client) { + let mut features = ExperimentalFeatures::new(&client); + features.set_logs_route(true); + let _ = features.update().await.unwrap(); + + let res = features.get().await.unwrap(); + assert!(res.logs_route) + } + + #[meilisearch_test] + async fn test_experimental_features_set_contains_filter(client: Client) { + let mut features = ExperimentalFeatures::new(&client); + features.set_contains_filter(true); + let _ = features.update().await.unwrap(); + + let res = features.get().await.unwrap(); + assert!(res.contains_filter) + } + + #[meilisearch_test] + async fn test_experimental_features_set_network(client: Client) { + let mut features = ExperimentalFeatures::new(&client); + features.set_network(true); + let _ = features.update().await.unwrap(); + + let res = features.get().await.unwrap(); + assert!(res.network) + } + + #[meilisearch_test] + async fn test_experimental_features_set_edit_documents_by_function(client: Client) { + let mut features = ExperimentalFeatures::new(&client); + features.set_edit_documents_by_function(true); let _ = features.update().await.unwrap(); - let _res = features.get().await.unwrap(); - // assert that the feature has been set once they exist again + let res = features.get().await.unwrap(); + assert!(res.edit_documents_by_function) } } diff --git a/src/lib.rs b/src/lib.rs index c0e4a74a..882043b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -244,6 +244,8 @@ pub mod features; pub mod indexes; /// Module containing the [`Key`](key::Key) struct. pub mod key; +// Module containing the [`Network`](network::Network) struct. +pub mod network; pub mod request; /// Module related to search queries and results. pub mod search; diff --git a/src/network.rs b/src/network.rs new file mode 100644 index 00000000..7075a71c --- /dev/null +++ b/src/network.rs @@ -0,0 +1,304 @@ +use std::collections::{BTreeMap, HashMap}; + +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +pub struct Network { + #[serde(rename = "self")] + pub self_: Option, + #[serde(deserialize_with = "network_deserializer")] + pub remotes: Vec, +} + +fn network_deserializer<'de, D>(d: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let map: BTreeMap> = Deserialize::deserialize(d)?; + + Ok(map + .into_iter() + .map(|(name, remote)| Remote { + name, + url: remote.get("url").cloned().unwrap_or_default(), + search_api_key: remote.get("searchApiKey").cloned(), + }) + .collect()) +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Remote { + #[serde(skip_serializing, skip_deserializing)] + pub name: String, + + pub url: String, + pub search_api_key: Option, +} + +#[derive(Serialize, Default)] +pub struct NetworkUpdate { + #[serde(rename = "self", skip_serializing_if = "Option::is_none")] + self_: Option>, + remotes: Option>>, +} + +impl NetworkUpdate { + #[must_use] + pub fn new() -> Self { + NetworkUpdate { + self_: None, + remotes: Some(BTreeMap::new()), + } + } + + pub fn reset_self(&mut self) -> &mut Self { + self.self_ = Some(None); + self + } + + pub fn reset_remotes(&mut self) -> &mut Self { + self.remotes = None; + self + } + + pub fn with_self(&mut self, new_self: &str) -> &mut Self { + self.self_ = Some(Some(new_self.to_string())); + self + } + + pub fn with_remotes(&mut self, new_remotes: &[Remote]) -> &mut Self { + if self.remotes.is_none() { + self.remotes = Some(BTreeMap::new()); + } + + self.remotes.as_mut().unwrap().extend( + new_remotes + .iter() + .map(|new_remote| (new_remote.name.clone(), Some(new_remote.clone()))), + ); + + self + } + + pub fn delete_remotes(&mut self, remotes_to_delete: &[&str]) -> &mut Self { + if self.remotes.is_none() { + self.remotes = Some(BTreeMap::new()); + } + + self.remotes.as_mut().unwrap().extend( + remotes_to_delete + .iter() + .map(|remote_name| (remote_name.to_string(), None)), + ); + + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_network() { + let example_json = r###" + { + "self": "ms-00", + "remotes": { + "ms-00": { + "url": "http://ms-1235.example.meilisearch.io", + "searchApiKey": "Ecd1SDDi4pqdJD6qYLxD3y7VZAEb4d9j6LJgt4d6xas" + }, + "ms-01": { + "url": "http://ms-4242.example.meilisearch.io", + "searchApiKey": "hrVu-OMcjPGElK7692K7bwriBoGyHXTMvB5NmZkMKqQ" + } + } + } + "###; + + let actual_remotes: Network = serde_json::from_str(example_json).unwrap(); + + let expected_remotes = Network { + self_: Some("ms-00".to_string()), + remotes: vec![ + Remote { + name: "ms-00".to_string(), + url: "http://ms-1235.example.meilisearch.io".to_string(), + search_api_key: Some("Ecd1SDDi4pqdJD6qYLxD3y7VZAEb4d9j6LJgt4d6xas".to_string()), + }, + Remote { + name: "ms-01".to_string(), + url: "http://ms-4242.example.meilisearch.io".to_string(), + search_api_key: Some("hrVu-OMcjPGElK7692K7bwriBoGyHXTMvB5NmZkMKqQ".to_string()), + }, + ], + }; + + assert_eq!(actual_remotes, expected_remotes); + } + + #[test] + fn test_serialize_network_update_reset_all() { + let mut expected_reset_self_json = r###" + { + "self": null, + "remotes": null + } + "### + .to_string(); + expected_reset_self_json.retain(|c| !c.is_whitespace()); + + let mut reset_self_json = + serde_json::to_string(NetworkUpdate::new().reset_self().reset_remotes()).unwrap(); + reset_self_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_reset_self_json, reset_self_json); + } + + #[test] + fn test_serialize_network_update_reset_self() { + let mut expected_reset_self_json = r###" + { + "self": null, + "remotes": {} + } + "### + .to_string(); + expected_reset_self_json.retain(|c| !c.is_whitespace()); + + let mut reset_self_json = serde_json::to_string(NetworkUpdate::new().reset_self()).unwrap(); + reset_self_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_reset_self_json, reset_self_json); + } + + #[test] + fn test_serialize_network_update_with_self() { + let mut expected_with_self_json = r###" + { + "self": "ms-00", + "remotes": {} + } + "### + .to_string(); + expected_with_self_json.retain(|c| !c.is_whitespace()); + + let mut with_self_json = + serde_json::to_string(NetworkUpdate::new().with_self("ms-00")).unwrap(); + with_self_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_with_self_json, with_self_json); + } + + #[test] + fn test_serialize_network_update_reset_remotes() { + let mut expected_reset_remotes_json = r###" + { + "remotes": null + } + "### + .to_string(); + expected_reset_remotes_json.retain(|c| !c.is_whitespace()); + + let mut reset_remotes_json = + serde_json::to_string(NetworkUpdate::new().reset_remotes()).unwrap(); + reset_remotes_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_reset_remotes_json, reset_remotes_json); + } + + #[test] + fn test_serialize_network_update_add_remotes() { + let mut expected_with_remotes_json = r###" + { + "remotes": { + "ms-00": { + "url": "http://localhost:7700", + "searchApiKey": "hello_world" + }, + "ms-01": { + "url": "http://localhost:7701", + "searchApiKey": "another_key" + } + } + } + "### + .to_string(); + expected_with_remotes_json.retain(|c| !c.is_whitespace()); + + let mut with_remotes_json = serde_json::to_string(NetworkUpdate::new().with_remotes(&[ + Remote { + name: "ms-00".to_string(), + url: "http://localhost:7700".to_string(), + search_api_key: Some("hello_world".to_string()), + }, + Remote { + name: "ms-01".to_string(), + url: "http://localhost:7701".to_string(), + search_api_key: Some("another_key".to_string()), + }, + ])) + .unwrap(); + with_remotes_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_with_remotes_json, with_remotes_json); + } + + #[test] + fn test_serialize_network_update_delete_remotes() { + let mut expected_with_remotes_json = r###" + { + "remotes": { + "ms-00": null, + "ms-01": null + } + } + "### + .to_string(); + expected_with_remotes_json.retain(|c| !c.is_whitespace()); + + let mut with_remotes_json = + serde_json::to_string(NetworkUpdate::new().delete_remotes(&["ms-00", "ms-01"])) + .unwrap(); + with_remotes_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_with_remotes_json, with_remotes_json); + } + + #[test] + fn test_serialize_network_update_operation_override() { + let mut expected_overridden_json = r###" + { + "remotes": { + "ms-00": null, + "ms-01": null + } + } + "### + .to_string(); + expected_overridden_json.retain(|c| !c.is_whitespace()); + + let mut overriden_json = serde_json::to_string( + NetworkUpdate::new() + .with_remotes(&[ + Remote { + name: "ms-00".to_string(), + url: "http://localhost:7700".to_string(), + search_api_key: Some("hello_world".to_string()), + }, + Remote { + name: "ms-01".to_string(), + url: "http://localhost:7701".to_string(), + search_api_key: Some("another_key".to_string()), + }, + ]) + .delete_remotes(&["ms-00", "ms-01"]), + ) + .unwrap(); + overriden_json.retain(|c| !c.is_whitespace()); + + assert_eq!(expected_overridden_json, overriden_json); + } +}