flagsmith_flag_engine/engine_eval/
mappers.rs

1use 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
13/// Maps an Environment to an EngineEvaluationContext
14///
15/// # Arguments
16/// * `environment` - The environment to convert
17///
18/// # Returns
19/// A new engine evaluation context
20pub 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    // Map feature states to feature contexts
32    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    // Map project segments to segment contexts
38    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    // Map identity overrides to segments
44    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
54/// Maps a FeatureState to a FeatureContext
55fn 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    // Set priority if this is a segment override
75    if let Some(feature_segment) = &fs.feature_segment {
76        fc.priority = Some(feature_segment.priority as f64);
77    }
78
79    fc
80}
81
82/// Maps multivariate feature state values to FeatureValue variants
83fn 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
96/// Maps a Segment to a SegmentContext
97fn 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    // Map feature state overrides
110    for fs in &segment.feature_states {
111        sc.overrides.push(map_feature_state_to_feature_context(fs));
112    }
113
114    // Map segment rules
115    for rule in &segment.rules {
116        sc.rules.push(map_segment_rule_to_rule(rule));
117    }
118
119    sc
120}
121
122/// Maps a legacy SegmentRule to the new SegmentRule format
123fn 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
149/// Maps a rule type string to SegmentRuleType enum
150fn 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
159/// Maps an operator string to ConditionOperator enum
160fn 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/// Helper struct for grouping identity overrides
181#[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
189/// Maps identity overrides to segment contexts
190fn 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        // Create override keys from features
200        let mut overrides = Vec::new();
201        for fs in &identity.identity_features {
202            // Use proper JSON serialization instead of Debug format
203            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        // Sort overrides for consistent hashing
214        overrides.sort();
215
216        // Generate hash for this set of overrides
217        let overrides_hash = generate_hash(&overrides);
218
219        // Group identifiers by their overrides
220        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    // Create segment contexts for each unique set of overrides
229    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        // Create segment context
235        let mut sc = SegmentContext {
236            key: String::new(), // Identity override segments never use % Split operator
237            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        // Create feature overrides
255        for override_key in overrides {
256            let priority = f64::NEG_INFINITY; // Strongest possible priority
257            let feature_override = FeatureContext {
258                key: String::new(), // Identity overrides never carry multivariate options
259                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
278/// Generates a hash from override keys for use as segment key
279fn 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    // Use safe slicing - take first 16 chars without panicking
294    let hex = format!("{:x}", result);
295    hex.chars().take(16).collect()
296}
297
298/// Adds identity data to an existing context
299///
300/// # Arguments
301/// * `context` - The context to enrich with identity data
302/// * `identifier` - The identity identifier
303/// * `traits` - The identity traits
304///
305/// # Returns
306/// A new context with identity information
307pub 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    // Create traits map
315    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    // Create identity context
321    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}