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