statsig_rust/evaluation/
evaluator_result.rs

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