flagsmith_flag_engine/engine_eval/
mappers.rs1use super::context::{
2 Condition, ConditionOperator, EngineEvaluationContext, EnvironmentContext, FeatureContext,
3 FeatureMetadata, FeatureValue, IdentityContext, SegmentContext, SegmentMetadata, SegmentRule,
4 SegmentRuleType, SegmentSource,
5};
6use crate::environments::Environment;
7use crate::features::{FeatureState, MultivariateFeatureStateValue};
8use crate::identities::{Identity, Trait};
9use crate::segments::{Segment, SegmentRule as OldSegmentRule};
10use sha2::{Digest, Sha256};
11use std::collections::HashMap;
12
13pub fn environment_to_context(environment: Environment) -> EngineEvaluationContext {
21 let mut ctx = EngineEvaluationContext {
22 environment: EnvironmentContext {
23 key: environment.api_key.clone(),
24 name: environment.name.clone(),
25 },
26 features: HashMap::new(),
27 segments: HashMap::new(),
28 identity: None,
29 };
30
31 for fs in &environment.feature_states {
33 let fc = map_feature_state_to_feature_context(fs);
34 ctx.features.insert(fc.name.clone(), fc);
35 }
36
37 for segment in &environment.project.segments {
39 let sc = map_segment_to_segment_context(segment);
40 ctx.segments.insert(sc.key.clone(), sc);
41 }
42
43 if !environment.identity_overrides.is_empty() {
45 let identity_segments = map_identity_overrides_to_segments(&environment.identity_overrides);
46 for (key, segment) in identity_segments {
47 ctx.segments.insert(key, segment);
48 }
49 }
50
51 ctx
52}
53
54fn map_feature_state_to_feature_context(fs: &FeatureState) -> FeatureContext {
56 let key = if let Some(django_id) = fs.django_id {
57 django_id.to_string()
58 } else {
59 fs.featurestate_uuid.clone()
60 };
61
62 let mut fc = FeatureContext {
63 enabled: fs.enabled,
64 key,
65 name: fs.feature.name.clone(),
66 value: fs.get_value(None),
67 priority: None,
68 variants: map_multivariate_values_to_variants(&fs.multivariate_feature_state_values),
69 metadata: FeatureMetadata {
70 feature_id: fs.feature.id,
71 feature_type: fs
72 .feature
73 .feature_type
74 .clone()
75 .unwrap_or_else(|| "STANDARD".to_string()),
76 },
77 };
78
79 if let Some(feature_segment) = &fs.feature_segment {
81 fc.priority = Some(feature_segment.priority as f64);
82 }
83
84 fc
85}
86
87fn map_multivariate_values_to_variants(
89 mv_values: &[MultivariateFeatureStateValue],
90) -> Vec<FeatureValue> {
91 mv_values
92 .iter()
93 .map(|mv| FeatureValue {
94 value: mv.multivariate_feature_option.value.clone(),
95 weight: mv.percentage_allocation as f64,
96 priority: None,
97 })
98 .collect()
99}
100
101fn map_segment_to_segment_context(segment: &Segment) -> SegmentContext {
103 let mut sc = SegmentContext {
104 key: segment.id.to_string(),
105 name: segment.name.clone(),
106 metadata: SegmentMetadata {
107 segment_id: Some(segment.id as i32),
108 source: SegmentSource::Api,
109 },
110 overrides: vec![],
111 rules: vec![],
112 };
113
114 for fs in &segment.feature_states {
116 sc.overrides.push(map_feature_state_to_feature_context(fs));
117 }
118
119 for rule in &segment.rules {
121 sc.rules.push(map_segment_rule_to_rule(rule));
122 }
123
124 sc
125}
126
127fn map_segment_rule_to_rule(rule: &OldSegmentRule) -> SegmentRule {
129 let rule_type = map_rule_type(&rule.segment_rule_type);
130
131 let conditions = rule
132 .conditions
133 .iter()
134 .map(|c| Condition {
135 operator: map_operator(&c.operator),
136 property: c.property.clone().unwrap_or_default(),
137 value: super::context::ConditionValue::Single(c.value.clone().unwrap_or_default()),
138 })
139 .collect();
140
141 let rules = rule
142 .rules
143 .iter()
144 .map(|r| map_segment_rule_to_rule(r))
145 .collect();
146
147 SegmentRule {
148 rule_type,
149 conditions,
150 rules,
151 }
152}
153
154fn map_rule_type(rule_type: &str) -> SegmentRuleType {
156 match rule_type {
157 "ALL" => SegmentRuleType::All,
158 "ANY" => SegmentRuleType::Any,
159 "NONE" => SegmentRuleType::None,
160 _ => SegmentRuleType::All,
161 }
162}
163
164fn map_operator(operator: &str) -> ConditionOperator {
166 match operator {
167 "EQUAL" => ConditionOperator::Equal,
168 "NOT_EQUAL" => ConditionOperator::NotEqual,
169 "GREATER_THAN" => ConditionOperator::GreaterThan,
170 "GREATER_THAN_INCLUSIVE" => ConditionOperator::GreaterThanInclusive,
171 "LESS_THAN" => ConditionOperator::LessThan,
172 "LESS_THAN_INCLUSIVE" => ConditionOperator::LessThanInclusive,
173 "CONTAINS" => ConditionOperator::Contains,
174 "NOT_CONTAINS" => ConditionOperator::NotContains,
175 "IN" => ConditionOperator::In,
176 "REGEX" => ConditionOperator::Regex,
177 "PERCENTAGE_SPLIT" => ConditionOperator::PercentageSplit,
178 "MODULO" => ConditionOperator::Modulo,
179 "IS_SET" => ConditionOperator::IsSet,
180 "IS_NOT_SET" => ConditionOperator::IsNotSet,
181 _ => ConditionOperator::Equal,
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
187struct OverrideKey {
188 feature_name: String,
189 enabled: String,
190 feature_value: String,
191 feature_id: u32,
192 feature_type: String,
193}
194
195fn map_identity_overrides_to_segments(identities: &[Identity]) -> HashMap<String, SegmentContext> {
197 let mut features_to_identifiers: HashMap<String, Vec<String>> = HashMap::new();
198 let mut overrides_key_to_list: HashMap<String, Vec<OverrideKey>> = HashMap::new();
199
200 for identity in identities {
201 if identity.identity_features.is_empty() {
202 continue;
203 }
204
205 let mut overrides = Vec::new();
207 for fs in &identity.identity_features {
208 let feature_value =
210 serde_json::to_string(&fs.get_value(None)).unwrap_or_else(|_| "null".to_string());
211 overrides.push(OverrideKey {
212 feature_name: fs.feature.name.clone(),
213 enabled: fs.enabled.to_string(),
214 feature_value,
215 feature_id: fs.feature.id,
216 feature_type: fs
217 .feature
218 .feature_type
219 .clone()
220 .unwrap_or_else(|| "STANDARD".to_string()),
221 });
222 }
223
224 overrides.sort();
226
227 let overrides_hash = generate_hash(&overrides);
229
230 features_to_identifiers
232 .entry(overrides_hash.clone())
233 .or_default()
234 .push(identity.identifier.clone());
235
236 overrides_key_to_list.insert(overrides_hash, overrides);
237 }
238
239 let mut segment_contexts = HashMap::new();
241
242 for (overrides_hash, identifiers) in features_to_identifiers {
243 let overrides = overrides_key_to_list.get(&overrides_hash).unwrap();
244
245 let mut sc = SegmentContext {
247 key: String::new(), name: "identity_overrides".to_string(),
249 metadata: SegmentMetadata {
250 segment_id: None,
251 source: SegmentSource::IdentityOverride,
252 },
253 overrides: vec![],
254 rules: vec![SegmentRule {
255 rule_type: SegmentRuleType::All,
256 conditions: vec![Condition {
257 operator: ConditionOperator::In,
258 property: "$.identity.identifier".to_string(),
259 value: super::context::ConditionValue::Multiple(identifiers),
260 }],
261 rules: vec![],
262 }],
263 };
264
265 for override_key in overrides {
267 let priority = f64::NEG_INFINITY; let feature_override = FeatureContext {
269 key: String::new(), name: override_key.feature_name.clone(),
271 enabled: override_key.enabled == "true",
272 value: serde_json::from_str(&override_key.feature_value).unwrap_or_default(),
273 priority: Some(priority),
274 variants: vec![],
275 metadata: FeatureMetadata {
276 feature_id: override_key.feature_id,
277 feature_type: override_key.feature_type.clone(),
278 },
279 };
280
281 sc.overrides.push(feature_override);
282 }
283
284 segment_contexts.insert(overrides_hash, sc);
285 }
286
287 segment_contexts
288}
289
290fn generate_hash(overrides: &[OverrideKey]) -> String {
292 let mut hasher = Sha256::new();
293
294 for override_key in overrides {
295 hasher.update(format!(
296 "{}:{}:{}:{};",
297 override_key.feature_id,
298 override_key.feature_name,
299 override_key.enabled,
300 override_key.feature_value
301 ));
302 }
303
304 let result = hasher.finalize();
305 let hex = format!("{:x}", result);
307 hex.chars().take(16).collect()
308}
309
310pub fn add_identity_to_context(
320 context: &EngineEvaluationContext,
321 identifier: &str,
322 traits: &[Trait],
323) -> EngineEvaluationContext {
324 let mut new_context = context.clone();
325
326 let mut identity_traits = HashMap::new();
328 for trait_obj in traits {
329 identity_traits.insert(trait_obj.trait_key.clone(), trait_obj.trait_value.clone());
330 }
331
332 let environment_key = &new_context.environment.key;
334 let identity = IdentityContext {
335 identifier: identifier.to_string(),
336 key: format!("{}_{}", environment_key, identifier),
337 traits: identity_traits,
338 };
339
340 new_context.identity = Some(identity);
341 new_context
342}