|
12 | 12 | // See the License for the specific language governing permissions and
|
13 | 13 | // limitations under the License.
|
14 | 14 |
|
15 |
| -use std::sync::Arc; |
16 |
| -use std::{io::Cursor, sync::Mutex}; |
| 15 | +use std::io::Cursor; |
17 | 16 |
|
18 | 17 | use murmur3::murmur3_32;
|
19 | 18 |
|
20 |
| -use crate::{ |
21 |
| - client::cache::ConfigurationSnapshot, models, |
22 |
| - segment_evaluation::find_applicable_segment_rule_for_entity, |
23 |
| -}; |
24 |
| - |
25 | 19 | use crate::entity::Entity;
|
| 20 | +use crate::Feature; |
26 | 21 |
|
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; |
30 | 24 |
|
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, |
36 | 27 | feature_id: String,
|
37 | 28 | }
|
38 | 29 |
|
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 } |
48 | 33 | }
|
49 | 34 |
|
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) |
124 | 37 | }
|
| 38 | +} |
125 | 39 |
|
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 |
137 | 43 | }
|
138 | 44 |
|
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() |
147 | 47 | }
|
148 | 48 |
|
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() |
157 | 51 | }
|
158 | 52 |
|
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() |
191 | 55 | }
|
192 | 56 |
|
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() |
205 | 61 | }
|
206 | 62 |
|
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) |
213 | 65 | }
|
214 | 66 | }
|
215 | 67 |
|
|
0 commit comments