diff --git a/README.md b/README.md index 2c71160..dd9ee0f 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,13 @@ from your [IBMCloud account](https://cloud.ibm.com/). Create your client with the context (environment and collection) you want to connect to ```rust -use appconfiguration_rust_sdk::{AppConfigurationClient, Entity, Result, Value, Feature}; +use appconfiguration_rust_sdk::{ + AppConfigurationClient, AppConfigurationClientIBMCloud, + Entity, Result, Value, Feature +}; // Create the client connecting to the server -let client = AppConfigurationClient::new(&apikey, ®ion, &guid, &environment_id, &collection_id)?; +let client = AppConfigurationClientIBMCloud::new(&apikey, ®ion, &guid, &environment_id, &collection_id)?; // Get the feature you want to evaluate for your entities let feature = client.get_feature("AB_testing_feature")?; diff --git a/examples/demo.rs b/examples/demo.rs index e272dc2..d42265f 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -14,7 +14,7 @@ use std::{collections::HashMap, env, thread, time::Duration}; -use appconfiguration_rust_sdk::{AppConfigurationClient, Entity, Feature, Property, Value}; +use appconfiguration_rust_sdk::{AppConfigurationClient, AppConfigurationClientIBMCloud, Entity, Feature, Property, Value}; use dotenvy::dotenv; use std::error::Error; @@ -48,8 +48,13 @@ fn main() -> std::result::Result<(), Box> { let feature_id = env::var("FEATURE_ID").expect("FEATURE_ID should be set."); let property_id = env::var("PROPERTY_ID").expect("PROPERTY_ID should be set."); - let client = - AppConfigurationClient::new(&apikey, ®ion, &guid, &environment_id, &collection_id)?; + let client = AppConfigurationClientIBMCloud::new( + &apikey, + ®ion, + &guid, + &environment_id, + &collection_id, + )?; let entity = CustomerEntity { id: "user123".to_string(), diff --git a/src/client/app_configuration_client.rs b/src/client/app_configuration_client.rs index f16ce36..51df36f 100644 --- a/src/client/app_configuration_client.rs +++ b/src/client/app_configuration_client.rs @@ -12,316 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::cache::ConfigurationSnapshot; +use crate::Result; + +use crate::client::feature_proxy::FeatureProxy; use crate::client::feature_snapshot::FeatureSnapshot; -pub use crate::client::feature_proxy::FeatureProxy; -use crate::client::http; +use crate::client::property_proxy::PropertyProxy; use crate::client::property_snapshot::PropertySnapshot; -pub use crate::client::property_proxy::PropertyProxy; -use crate::errors::{ConfigurationAccessError, Error, Result}; -use crate::models::Segment; -use std::collections::{HashMap, HashSet}; -use std::net::TcpStream; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::Duration; -use tungstenite::stream::MaybeTlsStream; -use tungstenite::Message; -use tungstenite::WebSocket; - -/// App Configuration client for browsing, and evaluating features and -/// properties. -#[derive(Debug)] -pub struct AppConfigurationClient { - pub(crate) latest_config_snapshot: Arc>, - pub(crate) _thread_terminator: std::sync::mpsc::Sender<()>, -} - -impl AppConfigurationClient { - /// Creates a client to retrieve configurations for a specific collection. - /// To uniquely address a collection the following is required: - /// - `region` - /// - `guid`: Identifies an instance - /// - `environment_id` - /// - `collection_id` - /// In addition `api_key` is required for authentication - pub fn new( - apikey: &str, - region: &str, - guid: &str, - environment_id: &str, - collection_id: &str, - ) -> Result { - let access_token = http::get_access_token(&apikey)?; - - // Populate initial configuration - let latest_config_snapshot: Arc> = - Arc::new(Mutex::new(Self::get_configuration_snapshot( - &access_token, - region, - guid, - environment_id, - collection_id, - )?)); - - // start monitoring configuration - let terminator = Self::update_cache_in_background( - latest_config_snapshot.clone(), - apikey, - region, - guid, - environment_id, - collection_id, - )?; - - let client = AppConfigurationClient { - latest_config_snapshot, - _thread_terminator: terminator, - }; - - Ok(client) - } - - fn get_configuration_snapshot( - access_token: &str, - region: &str, - guid: &str, - environment_id: &str, - collection_id: &str, - ) -> Result { - let configuration = http::get_configuration( - // TODO: access_token might expire. This will cause issues with long-running apps - &access_token, - ®ion, - &guid, - &collection_id, - &environment_id, - )?; - ConfigurationSnapshot::new(environment_id, configuration) - } - - fn wait_for_configuration_update( - socket: &mut WebSocket>, - access_token: &str, - region: &str, - guid: &str, - collection_id: &str, - environment_id: &str, - ) -> Result { - loop { - // read() blocks until something happens. - match socket.read()? { - Message::Text(text) => match text.as_str() { - "test message" => {} // periodically sent by the server - _ => { - return Self::get_configuration_snapshot( - access_token, - region, - guid, - environment_id, - collection_id, - ); - } - }, - Message::Close(_) => { - return Err(Error::Other("Connection closed by the server".into())); - } - _ => {} - } - } - } - - fn update_configuration_on_change( - mut socket: WebSocket>, - latest_config_snapshot: Arc>, - access_token: String, - region: String, - guid: String, - collection_id: String, - environment_id: String, - ) -> std::sync::mpsc::Sender<()> { - let (sender, receiver) = std::sync::mpsc::channel(); - - thread::spawn(move || { - loop { - // If the sender has gone (AppConfiguration instance is dropped), then finish this thread - if let Err(e) = receiver.try_recv() { - if e == std::sync::mpsc::TryRecvError::Disconnected { - break; - } - } - - let config_snapshot = Self::wait_for_configuration_update( - &mut socket, - &access_token, - ®ion, - &guid, - &collection_id, - &environment_id, - ); - - match config_snapshot { - Ok(config_snapshot) => *latest_config_snapshot.lock()? = config_snapshot, - Err(e) => { - println!("Waiting for configuration update failed. Stopping to monitor for changes.: {e}"); - break; - } - } - } - Ok::<(), Error>(()) - }); - sender - } +/// AppConfiguration client for browsing, and evaluating features and properties. +pub trait AppConfigurationClient { + fn get_feature_ids(&self) -> Result>; - pub fn get_feature_ids(&self) -> Result> { - Ok(self - .latest_config_snapshot - .lock()? - .features - .keys() - .cloned() - .collect()) - } - - pub fn get_feature(&self, feature_id: &str) -> Result { - let config_snapshot = self.latest_config_snapshot.lock()?; - - // Get the feature from the snapshot - let feature = config_snapshot.get_feature(feature_id)?; - - // Get the segment rules that apply to this feature - let segments = { - let all_segment_ids = feature - .segment_rules - .iter() - .flat_map(|targeting_rule| { - targeting_rule - .rules - .iter() - .flat_map(|segment| &segment.segments) - }) - .cloned() - .collect::>(); - let segments: HashMap = config_snapshot - .segments - .iter() - .filter(|&(key, _)| all_segment_ids.contains(key)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - - // Integrity DB check: all segment_ids should be available in the snapshot - if all_segment_ids.len() != segments.len() { - return Err(ConfigurationAccessError::MissingSegments { - resource_id: feature_id.to_string(), - } - .into()); - } - - segments - }; - - Ok(FeatureSnapshot::new(feature.clone(), segments)) - } + fn get_feature(&self, feature_id: &str) -> Result; /// Searches for the feature `feature_id` inside the current configured /// collection, and environment. /// /// Return `Ok(feature)` if the feature exists or `Err` if it does not. - pub fn get_feature_proxy<'a>(&'a self, feature_id: &str) -> Result> { - // FIXME: there is and was no validation happening if the feature exists. - // Comments and error messages in FeatureProxy suggest that this should happen here. - // same applies for properties. - Ok(FeatureProxy::new(self, feature_id.to_string())) - } - - pub fn get_property_ids(&self) -> Result> { - Ok(self - .latest_config_snapshot - .lock() - .map_err(|_| ConfigurationAccessError::LockAcquisitionError)? - .properties - .keys() - .cloned() - .collect()) - } + fn get_feature_proxy<'a>(&'a self, feature_id: &str) -> Result>; - pub fn get_property(&self, property_id: &str) -> Result { - let config_snapshot = self.latest_config_snapshot.lock()?; + fn get_property_ids(&self) -> Result>; - // Get the property from the snapshot - let property = config_snapshot.get_property(property_id)?; - - // Get the segment rules that apply to this property - let segments = { - let all_segment_ids = property - .segment_rules - .iter() - .flat_map(|targeting_rule| { - targeting_rule - .rules - .iter() - .flat_map(|segment| &segment.segments) - }) - .cloned() - .collect::>(); - let segments: HashMap = config_snapshot - .segments - .iter() - .filter(|&(key, _)| all_segment_ids.contains(key)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - - // Integrity DB check: all segment_ids should be available in the snapshot - if all_segment_ids.len() != segments.len() { - // FIXME: Return some kind of DBIntegrity error - return Err(ConfigurationAccessError::MissingSegments { - resource_id: property_id.to_string(), - } - .into()); - } - - segments - }; - - Ok(PropertySnapshot::new(property.clone(), segments)) - } + fn get_property(&self, property_id: &str) -> Result; /// Searches for the property `property_id` inside the current configured /// collection, and environment. /// /// Return `Ok(property)` if the feature exists or `Err` if it does not. - pub fn get_property_proxy(&self, property_id: &str) -> Result { - Ok(PropertyProxy::new(self, property_id.to_string())) - } - - fn update_cache_in_background( - latest_config_snapshot: Arc>, - apikey: &str, - region: &str, - guid: &str, - environment_id: &str, - collection_id: &str, - ) -> Result> { - let access_token = http::get_access_token(&apikey)?; - let (socket, _response) = http::get_configuration_monitoring_websocket( - &access_token, - ®ion, - &guid, - &collection_id, - &environment_id, - )?; - - let sender = Self::update_configuration_on_change( - socket, - latest_config_snapshot, - access_token, - region.to_string(), - guid.to_string(), - collection_id.to_string(), - environment_id.to_string(), - ); - - Ok(sender) - } + fn get_property_proxy(&self, property_id: &str) -> Result; } diff --git a/src/client/app_configuration_ibm_cloud.rs b/src/client/app_configuration_ibm_cloud.rs new file mode 100644 index 0000000..2593637 --- /dev/null +++ b/src/client/app_configuration_ibm_cloud.rs @@ -0,0 +1,325 @@ +// (C) Copyright IBM Corp. 2024. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::cache::ConfigurationSnapshot; +pub use crate::client::feature_proxy::FeatureProxy; +use crate::client::feature_snapshot::FeatureSnapshot; +use crate::client::http; +pub use crate::client::property_proxy::PropertyProxy; +use crate::client::property_snapshot::PropertySnapshot; +use crate::errors::{ConfigurationAccessError, Error, Result}; +use crate::models::Segment; +use std::collections::{HashMap, HashSet}; +use std::net::TcpStream; +use std::sync::{Arc, Mutex}; +use std::thread; + +use tungstenite::stream::MaybeTlsStream; +use tungstenite::Message; +use tungstenite::WebSocket; + +use super::AppConfigurationClient; + +/// AppConfiguration client connection to IBM Cloud. +#[derive(Debug)] +pub struct AppConfigurationClientIBMCloud { + pub(crate) latest_config_snapshot: Arc>, + pub(crate) _thread_terminator: std::sync::mpsc::Sender<()>, +} + +impl AppConfigurationClientIBMCloud { + + /// Creates a new [`AppConfigurationClient`] connecting to IBM Cloud + /// + /// # Arguments + /// + /// * `apikey` - The encrypted API key. + /// * `region` - Region name where the App Configuration service instance is created + /// * `guid` - Instance ID of the App Configuration service. Obtain it from the service credentials section of the App Configuration dashboard + /// * `environment_id` - ID of the environment created in App Configuration service instance under the Environments section. + /// * `collection_id` - ID of the collection created in App Configuration service instance under the Collections section + pub fn new( + apikey: &str, + region: &str, + guid: &str, + environment_id: &str, + collection_id: &str, + ) -> Result { + let access_token = http::get_access_token(apikey)?; + + // Populate initial configuration + let latest_config_snapshot: Arc> = + Arc::new(Mutex::new(Self::get_configuration_snapshot( + &access_token, + region, + guid, + environment_id, + collection_id, + )?)); + + // start monitoring configuration + let terminator = Self::update_cache_in_background( + latest_config_snapshot.clone(), + apikey, + region, + guid, + environment_id, + collection_id, + )?; + + let client = AppConfigurationClientIBMCloud { + latest_config_snapshot, + _thread_terminator: terminator, + }; + + Ok(client) + } + + fn get_configuration_snapshot( + access_token: &str, + region: &str, + guid: &str, + environment_id: &str, + collection_id: &str, + ) -> Result { + let configuration = http::get_configuration( + // TODO: access_token might expire. This will cause issues with long-running apps + &access_token, + ®ion, + &guid, + &collection_id, + &environment_id, + )?; + ConfigurationSnapshot::new(environment_id, configuration) + } + + fn wait_for_configuration_update( + socket: &mut WebSocket>, + access_token: &str, + region: &str, + guid: &str, + collection_id: &str, + environment_id: &str, + ) -> Result { + loop { + // read() blocks until something happens. + match socket.read()? { + Message::Text(text) => match text.as_str() { + "test message" => {} // periodically sent by the server + _ => { + return Self::get_configuration_snapshot( + access_token, + region, + guid, + environment_id, + collection_id, + ); + } + }, + Message::Close(_) => { + return Err(Error::Other("Connection closed by the server".into())); + } + _ => {} + } + } + } + + fn update_configuration_on_change( + mut socket: WebSocket>, + latest_config_snapshot: Arc>, + access_token: String, + region: String, + guid: String, + collection_id: String, + environment_id: String, + ) -> std::sync::mpsc::Sender<()> { + let (sender, receiver) = std::sync::mpsc::channel(); + + thread::spawn(move || { + loop { + // If the sender has gone (AppConfiguration instance is dropped), then finish this thread + if let Err(e) = receiver.try_recv() { + if e == std::sync::mpsc::TryRecvError::Disconnected { + break; + } + } + + let config_snapshot = Self::wait_for_configuration_update( + &mut socket, + &access_token, + ®ion, + &guid, + &collection_id, + &environment_id, + ); + + match config_snapshot { + Ok(config_snapshot) => *latest_config_snapshot.lock()? = config_snapshot, + Err(e) => { + println!("Waiting for configuration update failed. Stopping to monitor for changes.: {e}"); + break; + } + } + } + Ok::<(), Error>(()) + }); + + sender + } + + fn update_cache_in_background( + latest_config_snapshot: Arc>, + apikey: &str, + region: &str, + guid: &str, + environment_id: &str, + collection_id: &str, + ) -> Result> { + let access_token = http::get_access_token(&apikey)?; + let (socket, _response) = http::get_configuration_monitoring_websocket( + &access_token, + ®ion, + &guid, + &collection_id, + &environment_id, + )?; + + let sender = Self::update_configuration_on_change( + socket, + latest_config_snapshot, + access_token, + region.to_string(), + guid.to_string(), + collection_id.to_string(), + environment_id.to_string(), + ); + + Ok(sender) + } +} + +impl AppConfigurationClient for AppConfigurationClientIBMCloud { + fn get_feature_ids(&self) -> Result> { + Ok(self + .latest_config_snapshot + .lock()? + .features + .keys() + .cloned() + .collect()) + } + + fn get_feature(&self, feature_id: &str) -> Result { + let config_snapshot = self.latest_config_snapshot.lock()?; + + // Get the feature from the snapshot + let feature = config_snapshot.get_feature(feature_id)?; + + // Get the segment rules that apply to this feature + let segments = { + let all_segment_ids = feature + .segment_rules + .iter() + .flat_map(|targeting_rule| { + targeting_rule + .rules + .iter() + .flat_map(|segment| &segment.segments) + }) + .cloned() + .collect::>(); + let segments: HashMap = config_snapshot + .segments + .iter() + .filter(|&(key, _)| all_segment_ids.contains(key)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + // Integrity DB check: all segment_ids should be available in the snapshot + if all_segment_ids.len() != segments.len() { + return Err(ConfigurationAccessError::MissingSegments { + resource_id: feature_id.to_string(), + } + .into()); + } + + segments + }; + + Ok(FeatureSnapshot::new(feature.clone(), segments)) + } + + fn get_feature_proxy<'a>(&'a self, feature_id: &str) -> Result> { + // FIXME: there is and was no validation happening if the feature exists. + // Comments and error messages in FeatureProxy suggest that this should happen here. + // same applies for properties. + Ok(FeatureProxy::new(self, feature_id.to_string())) + } + + fn get_property_ids(&self) -> Result> { + Ok(self + .latest_config_snapshot + .lock() + .map_err(|_| ConfigurationAccessError::LockAcquisitionError)? + .properties + .keys() + .cloned() + .collect()) + } + + fn get_property(&self, property_id: &str) -> Result { + let config_snapshot = self.latest_config_snapshot.lock()?; + + // Get the property from the snapshot + let property = config_snapshot.get_property(property_id)?; + + // Get the segment rules that apply to this property + let segments = { + let all_segment_ids = property + .segment_rules + .iter() + .flat_map(|targeting_rule| { + targeting_rule + .rules + .iter() + .flat_map(|segment| &segment.segments) + }) + .cloned() + .collect::>(); + let segments: HashMap = config_snapshot + .segments + .iter() + .filter(|&(key, _)| all_segment_ids.contains(key)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + // Integrity DB check: all segment_ids should be available in the snapshot + if all_segment_ids.len() != segments.len() { + // FIXME: Return some kind of DBIntegrity error + return Err(ConfigurationAccessError::MissingSegments { + resource_id: property_id.to_string(), + } + .into()); + } + + segments + }; + + Ok(PropertySnapshot::new(property.clone(), segments)) + } + + fn get_property_proxy(&self, property_id: &str) -> Result { + Ok(PropertyProxy::new(self, property_id.to_string())) + } +} diff --git a/src/client/feature_proxy.rs b/src/client/feature_proxy.rs index 397bca0..64de168 100644 --- a/src/client/feature_proxy.rs +++ b/src/client/feature_proxy.rs @@ -23,12 +23,12 @@ use super::feature_snapshot::FeatureSnapshot; use super::AppConfigurationClient; pub struct FeatureProxy<'a> { - client: &'a AppConfigurationClient, + client: &'a dyn AppConfigurationClient, feature_id: String, } impl<'a> FeatureProxy<'a> { - pub(crate) fn new(client: &'a AppConfigurationClient, feature_id: String) -> Self { + pub(crate) fn new(client: &'a dyn AppConfigurationClient, feature_id: String) -> Self { Self { client, feature_id } } diff --git a/src/client/mod.rs b/src/client/mod.rs index a4d5d6a..78f0e29 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. mod app_configuration_client; +mod app_configuration_ibm_cloud; pub(crate) mod cache; pub(crate) mod feature_snapshot; @@ -23,5 +24,6 @@ pub(crate) mod property_proxy; pub use app_configuration_client::AppConfigurationClient; +pub use app_configuration_ibm_cloud::AppConfigurationClientIBMCloud; pub const REGION_US_SOUTH: &str = "us-south"; diff --git a/src/client/property_proxy.rs b/src/client/property_proxy.rs index 84adf34..6b2a23c 100644 --- a/src/client/property_proxy.rs +++ b/src/client/property_proxy.rs @@ -20,12 +20,12 @@ use crate::value::Value; use crate::Entity; pub struct PropertyProxy<'a> { - client: &'a AppConfigurationClient, + client: &'a dyn AppConfigurationClient, property_id: String, } impl<'a> PropertyProxy<'a> { - pub(crate) fn new(client: &'a AppConfigurationClient, property_id: String) -> Self { + pub(crate) fn new(client: &'a dyn AppConfigurationClient, property_id: String) -> Self { Self { client, property_id, diff --git a/src/feature.rs b/src/feature.rs index f941e78..29472b3 100644 --- a/src/feature.rs +++ b/src/feature.rs @@ -33,7 +33,7 @@ pub trait Feature { /// /// ``` /// # use appconfiguration_rust_sdk::{AppConfigurationClient, Feature, Result, Entity, Value}; - /// # fn doctest_get_value(client: AppConfigurationClient, entity: &impl Entity) -> Result<()> { + /// # fn doctest_get_value(client: impl AppConfigurationClient, entity: &impl Entity) -> Result<()> { /// let feature = client.get_feature("my_feature")?; /// let value: Value = feature.get_value(entity)?; /// @@ -55,7 +55,7 @@ pub trait Feature { /// /// ``` /// # use appconfiguration_rust_sdk::{AppConfigurationClient, Feature, Result, Entity}; - /// # fn doctest_get_value_into(client: AppConfigurationClient, entity: &impl Entity) -> Result<()> { + /// # fn doctest_get_value_into(client: impl AppConfigurationClient, entity: &impl Entity) -> Result<()> { /// let feature = client.get_feature("my_f64_feature")?; /// let value: f64 = feature.get_value_into(entity)?; /// diff --git a/src/lib.rs b/src/lib.rs index 8b37599..a68026c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,30 +16,33 @@ //! evaluation based on the configuration on IBM Cloud App Configuration service. //! //! # Overview -//! +//! //! [IBM Cloud App Configuration](https://cloud.ibm.com/docs/app-configuration) is a centralized //! feature management and configuration service on [IBM Cloud](https://www.cloud.ibm.com) for //! use with web and mobile applications, microservices, and distributed environments. -//! +//! //! Instrument your applications with App Configuration Rust SDK, and use the App Configuration //! dashboard, API or CLI to define feature flags or properties, organized into collections and //! targeted to segments. Change feature flag states in the cloud to activate or deactivate features //! in your application or environment, when required. You can also manage the properties for distributed //! applications centrally. -//! +//! //! # Pre-requisites -//! +//! //! You will need the `apikey`, `region` and `guid` for the AppConfiguration you want to connect to //! from your [IBMCloud account](https://cloud.ibm.com/). -//! +//! //! # Usage -//! +//! //! **Note.-** This crate is still under heavy development. Breaking changes are expected. -//! +//! //! Create your client with the context (environment and collection) you want to connect to -//! +//! //! ``` -//! use appconfiguration_rust_sdk::{AppConfigurationClient, Entity, Result, Value, Feature}; +//! use appconfiguration_rust_sdk::{ +//! AppConfigurationClient, AppConfigurationClientIBMCloud, +//! Entity, Result, Value, Feature +//! }; //! # use std::collections::HashMap; //! # pub struct MyEntity; //! # impl Entity for MyEntity { @@ -56,27 +59,27 @@ //! # let guid: &str = "12345678-1234-1234-1234-12345678abcd"; //! # let environment_id: &str = "production"; //! # let collection_id: &str = "ecommerce"; -//! +//! //! // Create the client connecting to the server -//! let client = AppConfigurationClient::new(&apikey, ®ion, &guid, &environment_id, &collection_id)?; -//! +//! let client = AppConfigurationClientIBMCloud::new(&apikey, ®ion, &guid, &environment_id, &collection_id)?; +//! //! // Get the feature you want to evaluate for your entities //! let feature = client.get_feature("AB_testing_feature")?; -//! +//! //! // Evaluate feature value for each one of your entities //! let user = MyEntity; // Implements Entity -//! +//! //! let value_for_this_user = feature.get_value(&user)?.try_into()?; //! if value_for_this_user { //! println!("Feature {} is active for user {}", feature.get_name()?, user.get_id()); //! } else { //! println!("User {} keeps using the legacy workflow", user.get_id()); //! } -//! +//! //! # Ok(()) //! # } //! ``` -//! +//! mod client; mod entity; mod errors; @@ -86,7 +89,7 @@ mod property; mod segment_evaluation; mod value; -pub use client::AppConfigurationClient; +pub use client::{AppConfigurationClient, AppConfigurationClientIBMCloud}; pub use entity::Entity; pub use errors::{Error, Result}; pub use feature::Feature; diff --git a/src/property.rs b/src/property.rs index 5aae2f7..516ea7c 100644 --- a/src/property.rs +++ b/src/property.rs @@ -26,7 +26,7 @@ pub trait Property { /// /// ``` /// # use appconfiguration_rust_sdk::{AppConfigurationClient, Property, Result, Entity, Value}; - /// # fn doctest_get_value(client: AppConfigurationClient, entity: &impl Entity) -> Result<()> { + /// # fn doctest_get_value(client: impl AppConfigurationClient, entity: &impl Entity) -> Result<()> { /// let property = client.get_property("my_property")?; /// let value: Value = property.get_value(entity)?; /// @@ -48,7 +48,7 @@ pub trait Property { /// /// ``` /// # use appconfiguration_rust_sdk::{AppConfigurationClient, Property, Result, Entity}; - /// # fn doctest_get_value_into(client: AppConfigurationClient, entity: &impl Entity) -> Result<()> { + /// # fn doctest_get_value_into(client: impl AppConfigurationClient, entity: &impl Entity) -> Result<()> { /// let property = client.get_property("my_bool_feature")?; /// let value: bool = property.get_value_into(entity)?; /// diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 583cbeb..8a0736d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -21,7 +21,7 @@ mod test_get_property_ids; mod test_using_example_data; use crate::client::cache::ConfigurationSnapshot; -use crate::client::AppConfigurationClient; +use crate::client::AppConfigurationClientIBMCloud; use crate::models::tests::example_configuration_enterprise; use crate::models::Configuration; use crate::Entity; @@ -57,14 +57,14 @@ impl Entity for GenericEntity { } #[fixture] -fn client_enterprise(example_configuration_enterprise: Configuration) -> AppConfigurationClient { +fn client_enterprise(example_configuration_enterprise: Configuration) -> AppConfigurationClientIBMCloud { let configuration_snapshot = ConfigurationSnapshot::new("dev", example_configuration_enterprise).unwrap(); // Create the client let (sender, _) = std::sync::mpsc::channel(); - AppConfigurationClient { + AppConfigurationClientIBMCloud { latest_config_snapshot: Arc::new(Mutex::new(configuration_snapshot)), _thread_terminator: sender, } diff --git a/src/tests/test_get_feature.rs b/src/tests/test_get_feature.rs index 0eb36a4..8d5382b 100644 --- a/src/tests/test_get_feature.rs +++ b/src/tests/test_get_feature.rs @@ -15,16 +15,16 @@ use crate::models::Configuration; use crate::client::cache::ConfigurationSnapshot; -use crate::client::AppConfigurationClient; +use crate::client::{AppConfigurationClient, AppConfigurationClientIBMCloud}; use rstest::*; use super::client_enterprise; -use crate::models::tests::configuration_feature1_enabled; use crate::feature::Feature; +use crate::models::tests::configuration_feature1_enabled; #[rstest] fn test_get_feature_persistence( - client_enterprise: AppConfigurationClient, + client_enterprise: AppConfigurationClientIBMCloud, configuration_feature1_enabled: Configuration, ) { let feature = client_enterprise.get_feature("f1").unwrap(); @@ -48,7 +48,7 @@ fn test_get_feature_persistence( } #[rstest] -fn test_get_feature_doesnt_exist(client_enterprise: AppConfigurationClient) { +fn test_get_feature_doesnt_exist(client_enterprise: AppConfigurationClientIBMCloud) { let feature = client_enterprise.get_feature("non-existing"); assert!(feature.is_err()); assert_eq!( diff --git a/src/tests/test_get_feature_ids.rs b/src/tests/test_get_feature_ids.rs index 4ecabfc..68ca36b 100644 --- a/src/tests/test_get_feature_ids.rs +++ b/src/tests/test_get_feature_ids.rs @@ -13,11 +13,11 @@ // limitations under the License. use super::client_enterprise; -use crate::client::AppConfigurationClient; +use crate::client::{AppConfigurationClient, AppConfigurationClientIBMCloud}; use rstest::*; #[rstest] -fn test_get_feature_ids(client_enterprise: AppConfigurationClient) { +fn test_get_feature_ids(client_enterprise: AppConfigurationClientIBMCloud) { let mut features = client_enterprise.get_feature_ids().unwrap(); features.sort(); assert_eq!( diff --git a/src/tests/test_get_property.rs b/src/tests/test_get_property.rs index 00ebd1f..1a17719 100644 --- a/src/tests/test_get_property.rs +++ b/src/tests/test_get_property.rs @@ -15,7 +15,7 @@ use crate::models::Configuration; use crate::client::cache::ConfigurationSnapshot; -use crate::client::AppConfigurationClient; +use crate::client::{AppConfigurationClient, AppConfigurationClientIBMCloud}; use rstest::*; use super::client_enterprise; @@ -24,7 +24,7 @@ use crate::property::Property; #[rstest] fn test_get_property_persistence( - client_enterprise: AppConfigurationClient, + client_enterprise: AppConfigurationClientIBMCloud, configuration_property1_enabled: Configuration, ) { let property = client_enterprise.get_property("p1").unwrap(); @@ -48,7 +48,7 @@ fn test_get_property_persistence( } #[rstest] -fn test_get_property_doesnt_exist(client_enterprise: AppConfigurationClient) { +fn test_get_property_doesnt_exist(client_enterprise: AppConfigurationClientIBMCloud) { let property = client_enterprise.get_property("non-existing"); assert!(property.is_err()); assert_eq!( diff --git a/src/tests/test_get_property_ids.rs b/src/tests/test_get_property_ids.rs index 36d0426..fe2be27 100644 --- a/src/tests/test_get_property_ids.rs +++ b/src/tests/test_get_property_ids.rs @@ -13,11 +13,11 @@ // limitations under the License. use super::client_enterprise; -use crate::client::AppConfigurationClient; +use crate::client::{AppConfigurationClient, AppConfigurationClientIBMCloud}; use rstest::*; #[rstest] -fn test_get_property_ids(client_enterprise: AppConfigurationClient) { +fn test_get_property_ids(client_enterprise: AppConfigurationClientIBMCloud) { let mut properties = client_enterprise.get_property_ids().unwrap(); properties.sort(); assert_eq!( diff --git a/src/tests/test_using_example_data.rs b/src/tests/test_using_example_data.rs index 82c1f82..8f93305 100644 --- a/src/tests/test_using_example_data.rs +++ b/src/tests/test_using_example_data.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::AppConfigurationClient; +use crate::client::{AppConfigurationClientIBMCloud, AppConfigurationClient}; use crate::tests::TrivialEntity; use rstest::*; @@ -20,7 +20,7 @@ use super::client_enterprise; use crate::{Feature, Property, Value}; #[rstest] -fn test_get_a_specific_feature(client_enterprise: AppConfigurationClient) { +fn test_get_a_specific_feature(client_enterprise: AppConfigurationClientIBMCloud) { let specific_feature = client_enterprise.get_feature_proxy("f1").unwrap(); let name = specific_feature.get_name().unwrap(); @@ -33,7 +33,7 @@ fn test_get_a_specific_feature(client_enterprise: AppConfigurationClient) { } #[rstest] -fn test_get_a_specific_property(client_enterprise: AppConfigurationClient) { +fn test_get_a_specific_property(client_enterprise: AppConfigurationClientIBMCloud) { let property = client_enterprise.get_property_proxy("p1").unwrap(); let name = property.get_name().unwrap(); diff --git a/tests/test_app_config.rs b/tests/test_app_config.rs index 5546500..990852f 100644 --- a/tests/test_app_config.rs +++ b/tests/test_app_config.rs @@ -16,7 +16,7 @@ use dotenvy::dotenv; use rstest::*; use appconfiguration_rust_sdk::{ - AppConfigurationClient, Entity, Feature, Property, Value, + AppConfigurationClient, AppConfigurationClientIBMCloud, Entity, Feature, Property, Value, }; use std::collections::HashMap; use std::env; @@ -34,7 +34,7 @@ impl Entity for TrivialEntity { } #[fixture] -fn setup_client() -> AppConfigurationClient { +fn setup_client() -> AppConfigurationClientIBMCloud { dotenv().expect( ".env file not found. Create one with the required variables in order to run the tests.", ); @@ -44,18 +44,18 @@ fn setup_client() -> AppConfigurationClient { //TODO: Our current pricing plan doesn't allow more than 1 collection, so we are using // car-rentals so far. - AppConfigurationClient::new(&apikey, ®ion, &guid, "testing", "car-rentals").unwrap() + AppConfigurationClientIBMCloud::new(&apikey, ®ion, &guid, "testing", "car-rentals").unwrap() } #[rstest] -fn test_get_list_of_features(setup_client: AppConfigurationClient) { +fn test_get_list_of_features(setup_client: AppConfigurationClientIBMCloud) { let features = setup_client.get_feature_ids().unwrap(); assert_eq!(features.len(), 4); } #[rstest] -fn test_get_a_specific_feature(setup_client: AppConfigurationClient) { +fn test_get_a_specific_feature(setup_client: AppConfigurationClientIBMCloud) { let specific_feature = setup_client .get_feature_proxy("test-feature-flag-1") .unwrap(); @@ -70,14 +70,14 @@ fn test_get_a_specific_feature(setup_client: AppConfigurationClient) { } #[rstest] -fn test_get_list_of_properties(setup_client: AppConfigurationClient) { +fn test_get_list_of_properties(setup_client: AppConfigurationClientIBMCloud) { let properties = setup_client.get_property_ids().unwrap(); assert_eq!(properties.len(), 2); } #[rstest] -fn test_get_a_specific_property(setup_client: AppConfigurationClient) { +fn test_get_a_specific_property(setup_client: AppConfigurationClientIBMCloud) { let property = setup_client.get_property_proxy("test-property-1").unwrap(); let name = property.get_name().unwrap();