Skip to content

Commit 4645800

Browse files
authored
Declare a Feature trait, re-implement FeatureProxy (#26)
* A new `Feature` trait that is implemented by our current `Feature` and `FeatureProxy`. * Rename our current `Feature` to `FeatureSnapshot`, to make it clear it will always return the same evaluation * Reimplement `FeatureProxy` making it much straightforward ```rust pub struct FeatureProxy<'a> { client: &'a AppConfigurationClient, feature_id: String, } ``` with this approach, every method can be implemented in terms of our current `Feature`: we just return a regular feature (with the configuration at that time) and call the method on it. ```rust fn get_value(&self, entity: &impl Entity) -> crate::errors::Result<super::value::Value> { self.client.get_feature(&self.feature_id)?.get_value(entity) } ```
2 parents 84e6254 + 9199999 commit 4645800

File tree

10 files changed

+141
-233
lines changed

10 files changed

+141
-233
lines changed

examples/demo.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
use std::{collections::HashMap, env, thread, time::Duration};
1616

17-
use appconfiguration_rust_sdk::{client::AppConfigurationClient, AttrValue, Entity};
17+
use appconfiguration_rust_sdk::{client::AppConfigurationClient, AttrValue, Entity, Feature};
1818
use dotenvy::dotenv;
1919
use std::error::Error;
2020

@@ -69,13 +69,13 @@ fn main() -> Result<()> {
6969

7070
match client.get_feature_proxy(&feature_id) {
7171
Ok(feature) => {
72-
println!("Feature name: {}", feature.get_name());
72+
println!("Feature name: {}", feature.get_name()?);
7373
println!("Feature id: {}", feature.get_id());
74-
println!("Feature data type: {}", feature.get_data_type());
75-
println!("Is feature enabled: {}", feature.is_enabled());
74+
println!("Feature data type: {}", feature.get_data_type()?);
75+
println!("Is feature enabled: {}", feature.is_enabled()?);
7676
println!(
77-
"Feature evaluated value is: {}",
78-
feature.get_current_value(&entity)
77+
"Feature evaluated value is: {:?}",
78+
feature.get_value(&entity)?
7979
);
8080
}
8181
Err(error) => {

src/client/app_configuration_client.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
use crate::client::cache::ConfigurationSnapshot;
16-
use crate::client::feature::Feature;
16+
use crate::client::feature_snapshot::FeatureSnapshot;
1717
pub use crate::client::feature_proxy::FeatureProxy;
1818
use crate::client::http;
1919
use crate::client::property::Property;
@@ -184,7 +184,7 @@ impl AppConfigurationClient {
184184
.collect())
185185
}
186186

187-
pub fn get_feature(&self, feature_id: &str) -> Result<Feature> {
187+
pub fn get_feature(&self, feature_id: &str) -> Result<FeatureSnapshot> {
188188
let config_snapshot = self.latest_config_snapshot.lock()?;
189189

190190
// Get the feature from the snapshot
@@ -221,21 +221,18 @@ impl AppConfigurationClient {
221221
segments
222222
};
223223

224-
Ok(Feature::new(feature.clone(), segments))
224+
Ok(FeatureSnapshot::new(feature.clone(), segments))
225225
}
226226

227227
/// Searches for the feature `feature_id` inside the current configured
228228
/// collection, and environment.
229229
///
230230
/// Return `Ok(feature)` if the feature exists or `Err` if it does not.
231-
pub fn get_feature_proxy(&self, feature_id: &str) -> Result<FeatureProxy> {
231+
pub fn get_feature_proxy<'a>(&'a self, feature_id: &str) -> Result<FeatureProxy<'a>> {
232232
// FIXME: there is and was no validation happening if the feature exists.
233233
// Comments and error messages in FeatureProxy suggest that this should happen here.
234234
// same applies for properties.
235-
Ok(FeatureProxy::new(
236-
self.latest_config_snapshot.clone(),
237-
feature_id.to_string(),
238-
))
235+
Ok(FeatureProxy::new(self, feature_id.to_string()))
239236
}
240237

241238
pub fn get_property_ids(&self) -> Result<Vec<String>> {

src/client/feature_proxy.rs

Lines changed: 27 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -12,204 +12,56 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use std::sync::Arc;
16-
use std::{io::Cursor, sync::Mutex};
15+
use std::io::Cursor;
1716

1817
use murmur3::murmur3_32;
1918

20-
use crate::{
21-
client::cache::ConfigurationSnapshot, models,
22-
segment_evaluation::find_applicable_segment_rule_for_entity,
23-
};
24-
2519
use crate::entity::Entity;
20+
use crate::Feature;
2621

27-
use crate::errors::ConfigurationAccessError;
28-
29-
const MISSING_FEATURE_ERROR_MSG: &str = "The feature should exist in the configuration_snapshot. It should have been validated in `AppConfigurationClient::get_feature()`.";
22+
use super::feature_snapshot::FeatureSnapshot;
23+
use super::AppConfigurationClient;
3024

31-
/// A feature in a collection and environment. Use the `get_feature()`
32-
/// method of the `AppConfigurationClient` to create instances of features.
33-
#[derive(Debug)]
34-
pub struct FeatureProxy {
35-
configuration_snapshot: Arc<Mutex<ConfigurationSnapshot>>,
25+
pub struct FeatureProxy<'a> {
26+
client: &'a AppConfigurationClient,
3627
feature_id: String,
3728
}
3829

39-
impl FeatureProxy {
40-
pub(crate) fn new(
41-
configuration_snapshot: Arc<Mutex<ConfigurationSnapshot>>,
42-
feature_id: String,
43-
) -> Self {
44-
FeatureProxy {
45-
configuration_snapshot,
46-
feature_id,
47-
}
30+
impl<'a> FeatureProxy<'a> {
31+
pub(crate) fn new(client: &'a AppConfigurationClient, feature_id: String) -> Self {
32+
Self { client, feature_id }
4833
}
4934

50-
/// Returns the name of the feature.
51-
pub fn get_name(&self) -> String {
52-
self.configuration_snapshot
53-
.lock()
54-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
55-
.get_feature(&self.feature_id)
56-
.expect(MISSING_FEATURE_ERROR_MSG)
57-
.name
58-
.clone()
59-
}
60-
61-
/// Returns the disable value as a `models::ConfigValue`.
62-
pub fn get_disabled_value(&self) -> models::ConfigValue {
63-
self.configuration_snapshot
64-
.lock()
65-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
66-
.get_feature(&self.feature_id)
67-
.expect(MISSING_FEATURE_ERROR_MSG)
68-
.disabled_value
69-
.clone()
70-
}
71-
72-
/// Returns the enabled value as a `models::ConfigValue`.
73-
pub fn get_enabled_value(&self) -> models::ConfigValue {
74-
self.configuration_snapshot
75-
.lock()
76-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
77-
.get_feature(&self.feature_id)
78-
.expect(MISSING_FEATURE_ERROR_MSG)
79-
.enabled_value
80-
.clone()
81-
}
82-
83-
/// Returns the id of the feature.
84-
pub fn get_id(&self) -> String {
85-
self.configuration_snapshot
86-
.lock()
87-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
88-
.get_feature(&self.feature_id)
89-
.expect(MISSING_FEATURE_ERROR_MSG)
90-
.feature_id
91-
.clone()
92-
}
93-
94-
/// Returns the data type as a member of the `models::ValueKind` enumeration.
95-
pub fn get_data_type(&self) -> models::ValueKind {
96-
self.configuration_snapshot
97-
.lock()
98-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
99-
.get_feature(&self.feature_id)
100-
.expect(MISSING_FEATURE_ERROR_MSG)
101-
.kind
102-
}
103-
104-
/// Gets the `Some(data_format)` if the feature data type is
105-
/// `models::ValueKind::STRING`, or `None` otherwise.
106-
pub fn get_data_format(&self) -> Option<String> {
107-
self.configuration_snapshot
108-
.lock()
109-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
110-
.get_feature(&self.feature_id)
111-
.expect(MISSING_FEATURE_ERROR_MSG)
112-
.format
113-
.clone()
114-
}
115-
116-
/// Returns the rollout peArcentage as a positive integer.
117-
pub fn get_rollout_percentage(&self) -> u32 {
118-
self.configuration_snapshot
119-
.lock()
120-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
121-
.get_feature(&self.feature_id)
122-
.expect(MISSING_FEATURE_ERROR_MSG)
123-
.rollout_percentage
35+
pub fn snapshot(&self) -> crate::errors::Result<FeatureSnapshot> {
36+
self.client.get_feature(&self.feature_id)
12437
}
38+
}
12539

126-
/// Returns the targeting rules for the feature. I.e.: what value to
127-
/// associate with an entity, under what ciArcumnstances, and how frequent
128-
/// it applies.
129-
pub fn get_targeting_rules(&self) -> Vec<models::TargetingRule> {
130-
self.configuration_snapshot
131-
.lock()
132-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
133-
.get_feature(&self.feature_id)
134-
.expect(MISSING_FEATURE_ERROR_MSG)
135-
.segment_rules
136-
.clone()
40+
impl<'a> Feature for FeatureProxy<'a> {
41+
fn get_id(&self) -> &str {
42+
&self.feature_id
13743
}
13844

139-
/// Returns if the feature is enabled or not.
140-
pub fn is_enabled(&self) -> bool {
141-
self.configuration_snapshot
142-
.lock()
143-
.unwrap_or_else(|_| panic!("{}", ConfigurationAccessError::LockAcquisitionError))
144-
.get_feature(&self.feature_id)
145-
.expect(MISSING_FEATURE_ERROR_MSG)
146-
.enabled
45+
fn get_name(&self) -> crate::errors::Result<String> {
46+
self.client.get_feature(&self.feature_id)?.get_name()
14747
}
14848

149-
/// Evaluates the feature for `entity` and returns the evaluation as a
150-
/// `models::ConfigValue`.
151-
pub fn get_current_value(&self, entity: &impl Entity) -> models::ConfigValue {
152-
if !self.is_enabled() {
153-
self.get_disabled_value()
154-
} else {
155-
self.evaluate_feature_for_entity(entity)
156-
}
49+
fn get_data_type(&self) -> crate::errors::Result<crate::models::ValueKind> {
50+
self.client.get_feature(&self.feature_id)?.get_data_type()
15751
}
15852

159-
fn evaluate_feature_for_entity(&self, entity: &impl Entity) -> models::ConfigValue {
160-
let tag = format!("{}:{}", entity.get_id(), self.get_id());
161-
162-
if self.get_targeting_rules().len() == 0 && entity.get_attributes().len() == 0 {
163-
// TODO rollout percentage evaluation
164-
}
165-
166-
let segment_rule = find_applicable_segment_rule_for_entity(
167-
&self
168-
.configuration_snapshot
169-
.lock()
170-
.unwrap_or_else(|e| panic!("Failed to acquire configuration snapshot lock: {e}"))
171-
.segments,
172-
self.get_targeting_rules().into_iter(),
173-
entity,
174-
)
175-
.unwrap_or_else(|e| panic!("Failed to evaluate segment rules: {e}"));
176-
if let Some(segment_rule) = segment_rule {
177-
let rollout_percentage = self.resolve_rollout_percentage(&segment_rule);
178-
if rollout_percentage == 100 || random_value(&tag) < rollout_percentage {
179-
self.resolve_enabled_value(&segment_rule)
180-
} else {
181-
self.get_disabled_value()
182-
}
183-
} else {
184-
let rollout_percentage = self.get_rollout_percentage();
185-
if rollout_percentage == 100 || random_value(&tag) < rollout_percentage {
186-
self.get_enabled_value()
187-
} else {
188-
self.get_disabled_value()
189-
}
190-
}
53+
fn is_enabled(&self) -> crate::errors::Result<bool> {
54+
self.client.get_feature(&self.feature_id)?.is_enabled()
19155
}
19256

193-
fn resolve_rollout_percentage(&self, segment_rule: &models::TargetingRule) -> u32 {
194-
let missing_rollout_msg = "Rollout velue is missing.";
195-
let rollout_value = segment_rule
196-
.rollout_percentage
197-
.as_ref()
198-
.expect(missing_rollout_msg);
199-
if rollout_value.is_default() {
200-
self.get_rollout_percentage()
201-
} else {
202-
u32::try_from(rollout_value.as_u64().expect(missing_rollout_msg))
203-
.expect("Invalid rollout value.")
204-
}
57+
fn get_enabled_value(&self) -> crate::errors::Result<crate::models::ConfigValue> {
58+
self.client
59+
.get_feature(&self.feature_id)?
60+
.get_enabled_value()
20561
}
20662

207-
fn resolve_enabled_value(&self, segment_rule: &models::TargetingRule) -> models::ConfigValue {
208-
if segment_rule.value.is_default() {
209-
self.get_enabled_value()
210-
} else {
211-
segment_rule.value.clone()
212-
}
63+
fn get_value(&self, entity: &impl Entity) -> crate::errors::Result<super::value::Value> {
64+
self.client.get_feature(&self.feature_id)?.get_value(entity)
21365
}
21466
}
21567

0 commit comments

Comments
 (0)