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