statsig_rust/evaluation/
evaluator_result.rs

1use crate::evaluation::evaluation_types::{
2    BaseEvaluation, DynamicConfigEvaluation, ExperimentEvaluation, GateEvaluation, LayerEvaluation,
3    SecondaryExposure,
4};
5use crate::hashing::{HashAlgorithm, HashUtil};
6use crate::specs_response::spec_types::Spec;
7use serde_json::Value;
8use std::collections::HashMap;
9
10use super::evaluation_types::ExtraExposureInfo;
11use super::evaluation_types_v2::{
12    BaseEvaluationV2, DynamicConfigEvaluationV2, ExperimentEvaluationV2, GateEvaluationV2,
13    LayerEvaluationV2,
14};
15
16#[derive(Default, Debug)]
17pub struct EvaluatorResult<'a> {
18    pub bool_value: bool,
19    pub unsupported: bool,
20    pub is_experiment_group: bool,
21    pub is_experiment_active: bool,
22    pub is_in_layer: bool,
23    pub id_type: Option<&'a String>,
24    pub json_value: Option<HashMap<String, Value>>,
25    pub rule_id: Option<&'a String>,
26    pub rule_id_suffix: Option<&'static str>,
27    pub group_name: Option<&'a String>,
28    pub explicit_parameters: Option<&'a Vec<String>>,
29    pub config_delegate: Option<&'a String>,
30    pub secondary_exposures: Vec<SecondaryExposure>,
31    pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
32    pub override_reason: Option<&'a str>,
33    pub version: Option<u32>,
34    pub sampling_rate: Option<u64>,
35    pub forward_all_exposures: Option<bool>,
36    pub override_config_name: Option<&'a str>,
37    pub has_seen_analytical_gates: Option<bool>,
38}
39
40pub fn result_to_gate_eval(gate_name: &str, result: &mut EvaluatorResult) -> GateEvaluation {
41    GateEvaluation {
42        base: result_to_base_eval(gate_name, result),
43        id_type: result.id_type.cloned().unwrap_or_default(),
44        value: result.bool_value,
45    }
46}
47
48pub fn result_to_gate_eval_v2(
49    gate_name: &str,
50    result: &mut EvaluatorResult,
51    hashing: &HashUtil,
52) -> GateEvaluationV2 {
53    GateEvaluationV2 {
54        base: result_to_base_eval_v2(gate_name, result, hashing),
55        id_type: result.id_type.cloned().unwrap_or_default(),
56        value: result.bool_value,
57    }
58}
59
60pub fn result_to_experiment_eval(
61    experiment_name: &str,
62    spec: Option<&Spec>,
63    result: &mut EvaluatorResult,
64) -> ExperimentEvaluation {
65    let (id_type, is_device_based) = get_id_type_info(result.id_type);
66
67    let mut is_experiment_active = None;
68    let mut is_user_in_experiment = None;
69
70    if let Some(spec) = spec {
71        if spec.entity == "experiment" {
72            is_experiment_active = Some(result.is_experiment_active);
73            is_user_in_experiment = Some(result.is_experiment_group);
74        }
75    }
76
77    ExperimentEvaluation {
78        base: result_to_base_eval(experiment_name, result),
79        id_type,
80        group: result.rule_id.cloned().unwrap_or_default(),
81        is_device_based,
82        value: get_json_value(result),
83        is_in_layer: result.is_in_layer,
84        group_name: result.group_name.cloned(),
85        explicit_parameters: result.explicit_parameters.cloned(),
86        is_experiment_active,
87        is_user_in_experiment,
88        undelegated_secondary_exposures: std::mem::take(
89            &mut result.undelegated_secondary_exposures,
90        ),
91    }
92}
93
94pub fn result_to_experiment_eval_v2(
95    experiment_name: &str,
96    spec: Option<&Spec>,
97    result: &mut EvaluatorResult,
98    hashing: &HashUtil,
99) -> ExperimentEvaluationV2 {
100    let (id_type, is_device_based) = get_id_type_info(result.id_type);
101
102    let mut is_experiment_active = None;
103    let mut is_user_in_experiment = None;
104
105    if let Some(spec) = spec {
106        if spec.entity == "experiment" {
107            is_experiment_active = Some(result.is_experiment_active);
108            is_user_in_experiment = Some(result.is_experiment_group);
109        }
110    }
111
112    ExperimentEvaluationV2 {
113        base: result_to_base_eval_v2(experiment_name, result, hashing),
114        id_type,
115        group: result.rule_id.cloned().unwrap_or_default(),
116        is_device_based,
117        value: get_json_value(result),
118        is_in_layer: result.is_in_layer,
119        group_name: result.group_name.cloned(),
120        explicit_parameters: result.explicit_parameters.cloned(),
121        is_experiment_active,
122        is_user_in_experiment,
123        undelegated_secondary_exposures: result.undelegated_secondary_exposures.clone(),
124    }
125}
126
127pub fn eval_result_to_experiment_eval(
128    experiment_name: &str,
129    result: &mut EvaluatorResult,
130) -> ExperimentEvaluation {
131    let (id_type, is_device_based) = get_id_type_info(result.id_type);
132
133    ExperimentEvaluation {
134        base: result_to_base_eval(experiment_name, result),
135        id_type,
136        group: result.rule_id.cloned().unwrap_or_default(),
137        is_device_based,
138        value: get_json_value(result),
139        is_in_layer: result.is_in_layer,
140        group_name: result.group_name.cloned(),
141        explicit_parameters: result.explicit_parameters.cloned(),
142        is_experiment_active: Some(result.is_experiment_active),
143        is_user_in_experiment: Some(result.is_experiment_group),
144        undelegated_secondary_exposures: std::mem::take(
145            &mut result.undelegated_secondary_exposures,
146        ),
147    }
148}
149
150pub fn result_to_layer_eval(layer_name: &str, result: &mut EvaluatorResult) -> LayerEvaluation {
151    let mut allocated_experiment_name = None;
152    let mut is_experiment_active = None;
153    let mut is_user_in_experiment = None;
154
155    if let Some(config_delegate) = result.config_delegate {
156        if !config_delegate.is_empty() {
157            allocated_experiment_name = Some(config_delegate.clone());
158            is_experiment_active = Some(result.is_experiment_active);
159            is_user_in_experiment = Some(result.is_experiment_group);
160        }
161    }
162
163    let (id_type, is_device_based) = get_id_type_info(result.id_type);
164    let undelegated_sec_expos = std::mem::take(&mut result.undelegated_secondary_exposures);
165
166    LayerEvaluation {
167        base: result_to_base_eval(layer_name, result),
168        group: result.rule_id.cloned().unwrap_or_default(),
169        value: get_json_value(result),
170        is_device_based,
171        group_name: result.group_name.cloned(),
172        is_experiment_active,
173        is_user_in_experiment,
174        allocated_experiment_name,
175        explicit_parameters: result.explicit_parameters.cloned().unwrap_or_default(),
176        undelegated_secondary_exposures: Some(undelegated_sec_expos.unwrap_or_default()),
177        id_type,
178    }
179}
180
181pub fn result_to_layer_eval_v2(
182    layer_name: &str,
183    result: &mut EvaluatorResult,
184    hashing: &HashUtil,
185) -> LayerEvaluationV2 {
186    let mut undelegated_secondary_exposures = Vec::new();
187
188    if let Some(u) = &result.undelegated_secondary_exposures {
189        for exposure in u {
190            let key = format!(
191                "{}:{}:{}",
192                exposure.gate, exposure.gate_value, exposure.rule_id
193            );
194            let hash = hashing.hash(&key, &HashAlgorithm::Djb2);
195            undelegated_secondary_exposures.push(hash.clone());
196        }
197    }
198
199    let mut allocated_experiment_name = None;
200    let mut is_experiment_active = None;
201    let mut is_user_in_experiment = None;
202
203    if let Some(config_delegate) = result.config_delegate {
204        if !config_delegate.is_empty() {
205            allocated_experiment_name = Some(config_delegate.clone());
206            is_experiment_active = Some(result.is_experiment_active);
207            is_user_in_experiment = Some(result.is_experiment_group);
208        }
209    }
210
211    let (id_type, is_device_based) = get_id_type_info(result.id_type);
212
213    LayerEvaluationV2 {
214        base: result_to_base_eval_v2(layer_name, result, hashing),
215        group: result.rule_id.cloned().unwrap_or_default(),
216        value: get_json_value(result),
217        is_device_based,
218        group_name: result.group_name.cloned(),
219        is_experiment_active,
220        is_user_in_experiment,
221        allocated_experiment_name,
222        explicit_parameters: result.explicit_parameters.cloned().unwrap_or_default(),
223        undelegated_secondary_exposures: Some(undelegated_secondary_exposures),
224        id_type,
225    }
226}
227
228pub fn result_to_dynamic_config_eval(
229    dynamic_config_name: &str,
230    result: &mut EvaluatorResult,
231) -> DynamicConfigEvaluation {
232    let (id_type, is_device_based) = get_id_type_info(result.id_type);
233
234    DynamicConfigEvaluation {
235        base: result_to_base_eval(dynamic_config_name, result),
236        id_type,
237        is_device_based,
238        value: get_json_value(result),
239        group: result.rule_id.cloned().unwrap_or_default(),
240        passed: result.bool_value,
241    }
242}
243
244pub fn result_to_dynamic_config_eval_v2(
245    dynamic_config_name: &str,
246    result: &mut EvaluatorResult,
247    hashing: &HashUtil,
248) -> DynamicConfigEvaluationV2 {
249    let (id_type, is_device_based) = get_id_type_info(result.id_type);
250
251    DynamicConfigEvaluationV2 {
252        base: result_to_base_eval_v2(dynamic_config_name, result, hashing),
253        id_type,
254        is_device_based,
255        value: get_json_value(result),
256        group: result.rule_id.cloned().unwrap_or_default(),
257        passed: result.bool_value,
258    }
259}
260
261fn get_id_type_info(id_type: Option<&String>) -> (String, bool) {
262    let id_type = id_type.cloned().unwrap_or_default();
263    let is_device_based = id_type == "stableID" || id_type == "stableid";
264    (id_type, is_device_based)
265}
266
267fn get_json_value(result: &EvaluatorResult) -> HashMap<String, Value> {
268    result.json_value.clone().unwrap_or_default()
269}
270
271fn result_to_base_eval(spec_name: &str, result: &mut EvaluatorResult) -> BaseEvaluation {
272    let result_rule_id = result.rule_id.map(|r| r.as_str()).unwrap_or_default();
273
274    let rule_id = match &result.rule_id_suffix {
275        Some(suffix) => format!("{result_rule_id}:{suffix}"),
276        None => result_rule_id.to_string(),
277    };
278
279    let exposure_info = ExtraExposureInfo {
280        sampling_rate: result.sampling_rate,
281        forward_all_exposures: result.forward_all_exposures,
282        has_seen_analytical_gates: result.has_seen_analytical_gates,
283        override_config_name: result.override_config_name.map(|s| s.to_string()),
284        version: result.version,
285    };
286
287    BaseEvaluation {
288        name: spec_name.to_string(),
289        rule_id,
290        secondary_exposures: std::mem::take(&mut result.secondary_exposures),
291        exposure_info: Some(exposure_info),
292    }
293}
294
295fn result_to_base_eval_v2(
296    spec_name: &str,
297    result: &mut EvaluatorResult,
298    hashing: &HashUtil,
299) -> BaseEvaluationV2 {
300    let mut exposures = Vec::new();
301
302    for exposure in &result.secondary_exposures {
303        let key = format!(
304            "{}:{}:{}",
305            exposure.gate, exposure.gate_value, exposure.rule_id
306        );
307        let hash = hashing.hash(&key, &HashAlgorithm::Djb2);
308        exposures.push(hash.clone());
309    }
310
311    let rule_id = match result.rule_id {
312        Some(rule_id) => rule_id.clone(),
313        None => String::new(),
314    };
315
316    let result_rule_id = match &result.rule_id_suffix {
317        Some(suffix) => format!("{rule_id}:{suffix}"),
318        None => rule_id.clone(),
319    };
320
321    BaseEvaluationV2 {
322        name: spec_name.to_string(),
323        rule_id: result_rule_id.clone(),
324        secondary_exposures: exposures,
325    }
326}