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 },
72 };
73
74 if let Some(feature_segment) = &fs.feature_segment {
76 fc.priority = Some(feature_segment.priority as f64);
77 }
78
79 fc
80}
81
82fn map_multivariate_values_to_variants(
84 mv_values: &[MultivariateFeatureStateValue],
85) -> Vec<FeatureValue> {
86 mv_values
87 .iter()
88 .map(|mv| FeatureValue {
89 value: mv.multivariate_feature_option.value.clone(),
90 weight: mv.percentage_allocation as f64,
91 priority: None,
92 })
93 .collect()
94}
95
96fn map_segment_to_segment_context(segment: &Segment) -> SegmentContext {
98 let mut sc = SegmentContext {
99 key: segment.id.to_string(),
100 name: segment.name.clone(),
101 metadata: SegmentMetadata {
102 segment_id: Some(segment.id as i32),
103 source: SegmentSource::Api,
104 },
105 overrides: vec![],
106 rules: vec![],
107 };
108
109 for fs in &segment.feature_states {
111 sc.overrides.push(map_feature_state_to_feature_context(fs));
112 }
113
114 for rule in &segment.rules {
116 sc.rules.push(map_segment_rule_to_rule(rule));
117 }
118
119 sc
120}
121
122fn map_segment_rule_to_rule(rule: &OldSegmentRule) -> SegmentRule {
124 let rule_type = map_rule_type(&rule.segment_rule_type);
125
126 let conditions = rule
127 .conditions
128 .iter()
129 .map(|c| Condition {
130 operator: map_operator(&c.operator),
131 property: c.property.clone().unwrap_or_default(),
132 value: super::context::ConditionValue::Single(c.value.clone().unwrap_or_default()),
133 })
134 .collect();
135
136 let rules = rule
137 .rules
138 .iter()
139 .map(|r| map_segment_rule_to_rule(r))
140 .collect();
141
142 SegmentRule {
143 rule_type,
144 conditions,
145 rules,
146 }
147}
148
149fn map_rule_type(rule_type: &str) -> SegmentRuleType {
151 match rule_type {
152 "ALL" => SegmentRuleType::All,
153 "ANY" => SegmentRuleType::Any,
154 "NONE" => SegmentRuleType::None,
155 _ => SegmentRuleType::All,
156 }
157}
158
159fn map_operator(operator: &str) -> ConditionOperator {
161 match operator {
162 "EQUAL" => ConditionOperator::Equal,
163 "NOT_EQUAL" => ConditionOperator::NotEqual,
164 "GREATER_THAN" => ConditionOperator::GreaterThan,
165 "GREATER_THAN_INCLUSIVE" => ConditionOperator::GreaterThanInclusive,
166 "LESS_THAN" => ConditionOperator::LessThan,
167 "LESS_THAN_INCLUSIVE" => ConditionOperator::LessThanInclusive,
168 "CONTAINS" => ConditionOperator::Contains,
169 "NOT_CONTAINS" => ConditionOperator::NotContains,
170 "IN" => ConditionOperator::In,
171 "REGEX" => ConditionOperator::Regex,
172 "PERCENTAGE_SPLIT" => ConditionOperator::PercentageSplit,
173 "MODULO" => ConditionOperator::Modulo,
174 "IS_SET" => ConditionOperator::IsSet,
175 "IS_NOT_SET" => ConditionOperator::IsNotSet,
176 _ => ConditionOperator::Equal,
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
182struct OverrideKey {
183 feature_name: String,
184 enabled: String,
185 feature_value: String,
186 feature_id: u32,
187}
188
189fn map_identity_overrides_to_segments(identities: &[Identity]) -> HashMap<String, SegmentContext> {
191 let mut features_to_identifiers: HashMap<String, Vec<String>> = HashMap::new();
192 let mut overrides_key_to_list: HashMap<String, Vec<OverrideKey>> = HashMap::new();
193
194 for identity in identities {
195 if identity.identity_features.is_empty() {
196 continue;
197 }
198
199 let mut overrides = Vec::new();
201 for fs in &identity.identity_features {
202 let feature_value =
204 serde_json::to_string(&fs.get_value(None)).unwrap_or_else(|_| "null".to_string());
205 overrides.push(OverrideKey {
206 feature_name: fs.feature.name.clone(),
207 enabled: fs.enabled.to_string(),
208 feature_value,
209 feature_id: fs.feature.id,
210 });
211 }
212
213 overrides.sort();
215
216 let overrides_hash = generate_hash(&overrides);
218
219 features_to_identifiers
221 .entry(overrides_hash.clone())
222 .or_default()
223 .push(identity.identifier.clone());
224
225 overrides_key_to_list.insert(overrides_hash, overrides);
226 }
227
228 let mut segment_contexts = HashMap::new();
230
231 for (overrides_hash, identifiers) in features_to_identifiers {
232 let overrides = overrides_key_to_list.get(&overrides_hash).unwrap();
233
234 let mut sc = SegmentContext {
236 key: String::new(), name: "identity_overrides".to_string(),
238 metadata: SegmentMetadata {
239 segment_id: None,
240 source: SegmentSource::IdentityOverride,
241 },
242 overrides: vec![],
243 rules: vec![SegmentRule {
244 rule_type: SegmentRuleType::All,
245 conditions: vec![Condition {
246 operator: ConditionOperator::In,
247 property: "$.identity.identifier".to_string(),
248 value: super::context::ConditionValue::Multiple(identifiers),
249 }],
250 rules: vec![],
251 }],
252 };
253
254 for override_key in overrides {
256 let priority = f64::NEG_INFINITY; let feature_override = FeatureContext {
258 key: String::new(), name: override_key.feature_name.clone(),
260 enabled: override_key.enabled == "true",
261 value: serde_json::from_str(&override_key.feature_value).unwrap_or_default(),
262 priority: Some(priority),
263 variants: vec![],
264 metadata: FeatureMetadata {
265 feature_id: override_key.feature_id,
266 },
267 };
268
269 sc.overrides.push(feature_override);
270 }
271
272 segment_contexts.insert(overrides_hash, sc);
273 }
274
275 segment_contexts
276}
277
278fn generate_hash(overrides: &[OverrideKey]) -> String {
280 let mut hasher = Sha256::new();
281
282 for override_key in overrides {
283 hasher.update(format!(
284 "{}:{}:{}:{};",
285 override_key.feature_id,
286 override_key.feature_name,
287 override_key.enabled,
288 override_key.feature_value
289 ));
290 }
291
292 let result = hasher.finalize();
293 let hex = format!("{:x}", result);
295 hex.chars().take(16).collect()
296}
297
298pub fn add_identity_to_context(
308 context: &EngineEvaluationContext,
309 identifier: &str,
310 traits: &[Trait],
311) -> EngineEvaluationContext {
312 let mut new_context = context.clone();
313
314 let mut identity_traits = HashMap::new();
316 for trait_obj in traits {
317 identity_traits.insert(trait_obj.trait_key.clone(), trait_obj.trait_value.clone());
318 }
319
320 let environment_key = &new_context.environment.key;
322 let identity = IdentityContext {
323 identifier: identifier.to_string(),
324 key: format!("{}_{}", environment_key, identifier),
325 traits: identity_traits,
326 };
327
328 new_context.identity = Some(identity);
329 new_context
330}