1use std::collections::HashMap;
2
3use ahash::AHashMap;
4
5use super::dynamic_returnable::DynamicReturnable;
6use super::evaluation_types::ExtraExposureInfo;
7use super::evaluation_types_v2::{
8 BaseEvaluationV2, DynamicConfigEvaluationV2, ExperimentEvaluationV2, GateEvaluationV2,
9 LayerEvaluationV2,
10};
11use crate::evaluation::evaluation_types::{
12 BaseEvaluation, DynamicConfigEvaluation, ExperimentEvaluation, GateEvaluation, LayerEvaluation,
13 SecondaryExposure,
14};
15use crate::evaluation::evaluation_types_initialize_v2::{
16 BaseEvaluationInitV2, DynamicConfigEvaluationInitV2, ExperimentEvaluationInitV2,
17 GateEvaluationInitV2, LayerEvaluationInitV2,
18};
19use crate::evaluation::secondary_exposure_key::SecondaryExposureKey;
20use crate::hashing::{HashAlgorithm, HashUtil};
21use crate::interned_string::InternedString;
22
23#[derive(Default, Debug)]
24pub struct EvaluatorResult<'a> {
25 pub name: Option<&'a InternedString>,
26 pub bool_value: bool,
27 pub unsupported: bool,
28 pub is_experiment_group: bool,
29 pub is_experiment_active: bool,
30 pub is_in_layer: bool,
31 pub is_in_experiment: bool,
32 pub id_type: Option<InternedString>,
33 pub json_value: Option<DynamicReturnable>,
34 pub rule_id: Option<&'a InternedString>,
35 pub rule_id_suffix: Option<&'static str>,
36 pub group_name: Option<InternedString>,
37 pub explicit_parameters: Option<&'a Vec<InternedString>>,
38 pub config_delegate: Option<InternedString>,
39 pub secondary_exposures: Vec<SecondaryExposure>,
40 pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
41 pub override_reason: Option<&'a str>,
42 pub version: Option<u32>,
43 pub sampling_rate: Option<u64>,
44 pub forward_all_exposures: Option<bool>,
45 pub override_config_name: Option<&'a str>,
46 pub has_seen_analytical_gates: Option<bool>,
47}
48
49pub fn result_to_gate_eval(gate_name: &str, result: &mut EvaluatorResult) -> GateEvaluation {
50 GateEvaluation {
51 base: result_to_base_eval(gate_name, result),
52 id_type: result.id_type.take(),
53 value: result.bool_value,
54 }
55}
56
57pub fn result_to_gate_eval_v2(
58 gate_name: &str,
59 result: &mut EvaluatorResult,
60 hashing: &HashUtil,
61) -> GateEvaluationV2 {
62 GateEvaluationV2 {
63 base: result_to_base_eval_v2(gate_name, result, hashing),
64 id_type: result.id_type.take(),
65 value: result.bool_value,
66 }
67}
68
69pub fn result_to_gate_eval_init_v2(
70 result: &mut EvaluatorResult,
71 expo_id_to_exposure_map: &mut HashMap<InternedString, SecondaryExposure>,
72 expo_key_to_expo_id_map: &mut AHashMap<SecondaryExposureKey, InternedString>,
73) -> GateEvaluationInitV2 {
74 let value: Option<bool> = match result.bool_value {
75 true => Some(true),
76 _ => None,
77 };
78 GateEvaluationInitV2 {
79 base: result_to_base_eval_init_v2(result, expo_id_to_exposure_map, expo_key_to_expo_id_map),
80 id_type: result.id_type.take(),
81 value,
82 }
83}
84
85pub fn result_to_experiment_eval(
86 experiment_name: &str,
87 spec_entity: Option<&str>,
88 result: &mut EvaluatorResult,
89) -> ExperimentEvaluation {
90 let (id_type, is_device_based) = get_id_type_info(result.id_type.as_ref());
91
92 let mut is_experiment_active = None;
93 let mut is_user_in_experiment = None;
94
95 if spec_entity.is_none_or(|s| s == "experiment") {
96 is_experiment_active = Some(result.is_experiment_active);
97 is_user_in_experiment = Some(result.is_experiment_group);
98 }
99
100 ExperimentEvaluation {
101 base: result_to_base_eval(experiment_name, result),
102 id_type: Some(id_type),
103 is_device_based,
104 value: get_json_value(result),
105 is_in_layer: result.is_in_layer,
106 group_name: result.group_name.take(),
107 explicit_parameters: result.explicit_parameters.cloned(),
108 is_experiment_active,
109 is_user_in_experiment,
110 undelegated_secondary_exposures: std::mem::take(
111 &mut result.undelegated_secondary_exposures,
112 ),
113 }
114}
115
116pub fn result_to_experiment_eval_v2(
117 experiment_name: &str,
118 spec_entity: Option<&str>,
119 result: &mut EvaluatorResult,
120 hashing: &HashUtil,
121) -> ExperimentEvaluationV2 {
122 let (id_type, is_device_based) = get_id_type_info(result.id_type.as_ref());
123
124 let mut is_experiment_active = None;
125 let mut is_user_in_experiment = None;
126
127 if let Some(spec_entity) = spec_entity {
128 if spec_entity == "experiment" {
129 is_experiment_active = Some(result.is_experiment_active);
130 is_user_in_experiment = Some(result.is_experiment_group);
131 }
132 }
133
134 ExperimentEvaluationV2 {
135 base: result_to_base_eval_v2(experiment_name, result, hashing),
136 id_type: Some(id_type),
137 is_device_based,
138 value: get_json_value(result),
139 is_in_layer: result.is_in_layer,
140 group_name: result.group_name.take(),
141 explicit_parameters: result.explicit_parameters.cloned(),
142 is_experiment_active,
143 is_user_in_experiment,
144 undelegated_secondary_exposures: result.undelegated_secondary_exposures.clone(),
145 }
146}
147
148pub fn result_to_experiment_eval_init_v2(
149 spec_entity: Option<&str>,
150 result: &mut EvaluatorResult,
151 expo_id_to_exposure_map: &mut HashMap<InternedString, SecondaryExposure>,
152 expo_key_to_expo_id_map: &mut AHashMap<SecondaryExposureKey, InternedString>,
153 value_id_to_value_map: &mut HashMap<InternedString, DynamicReturnable>,
154 value_key_to_value_id: &mut AHashMap<u64, InternedString>,
155) -> ExperimentEvaluationInitV2 {
156 let value: Option<DynamicReturnable> = result.json_value.take();
157
158 let id_type = result.id_type.take();
159
160 let mut is_experiment_active = None;
161 let mut is_user_in_experiment = None;
162
163 if let Some(spec_entity) = spec_entity {
164 if spec_entity == "experiment" {
165 is_experiment_active = Some(result.is_experiment_active);
166 is_user_in_experiment = Some(result.is_experiment_group);
167 }
168 }
169
170 let mapped_value = self::get_mapped_value(value, value_id_to_value_map, value_key_to_value_id);
171
172 ExperimentEvaluationInitV2 {
173 base: result_to_base_eval_init_v2(result, expo_id_to_exposure_map, expo_key_to_expo_id_map),
174 id_type,
175 value: mapped_value,
176 group_name: result.group_name.take(),
177 is_experiment_active,
178 is_user_in_experiment,
179 }
180}
181
182pub fn eval_result_to_experiment_eval(
183 experiment_name: &str,
184 result: &mut EvaluatorResult,
185) -> ExperimentEvaluation {
186 let (id_type, is_device_based) = get_id_type_info(result.id_type.as_ref());
187
188 ExperimentEvaluation {
189 base: result_to_base_eval(experiment_name, result),
190 id_type: Some(id_type),
191 is_device_based,
192 value: get_json_value(result),
193 is_in_layer: result.is_in_layer,
194 group_name: result.group_name.take(),
195 explicit_parameters: result.explicit_parameters.cloned(),
196 is_experiment_active: Some(result.is_experiment_active),
197 is_user_in_experiment: Some(result.is_experiment_group),
198 undelegated_secondary_exposures: std::mem::take(
199 &mut result.undelegated_secondary_exposures,
200 ),
201 }
202}
203
204pub fn result_to_layer_eval(layer_name: &str, result: &mut EvaluatorResult) -> LayerEvaluation {
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.take() {
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.as_ref());
218 let undelegated_sec_expos = std::mem::take(&mut result.undelegated_secondary_exposures);
219
220 LayerEvaluation {
221 base: result_to_base_eval(layer_name, result),
222 value: get_json_value(result),
223 is_device_based,
224 group_name: result.group_name.take(),
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_sec_expos.unwrap_or_default()),
230 id_type: Some(id_type),
231 }
232}
233
234pub fn result_to_layer_eval_v2(
235 layer_name: &str,
236 result: &mut EvaluatorResult,
237 hashing: &HashUtil,
238) -> LayerEvaluationV2 {
239 let mut undelegated_secondary_exposures = Vec::new();
240
241 if let Some(u) = &result.undelegated_secondary_exposures {
242 for exposure in u {
243 let key = format!(
244 "{}:{}:{}",
245 exposure.gate,
246 exposure.gate_value,
247 exposure.rule_id.as_str()
248 );
249 let hash = hashing.hash(&key, &HashAlgorithm::Djb2);
250 undelegated_secondary_exposures.push(InternedString::from_string(hash));
251 }
252 }
253
254 let mut allocated_experiment_name = None;
255 let mut is_experiment_active = None;
256 let mut is_user_in_experiment = None;
257
258 if let Some(config_delegate) = result.config_delegate.take() {
259 if !config_delegate.is_empty() {
260 allocated_experiment_name = Some(config_delegate.clone());
261 is_experiment_active = Some(result.is_experiment_active);
262 is_user_in_experiment = Some(result.is_experiment_group);
263 }
264 }
265
266 let (id_type, is_device_based) = get_id_type_info(result.id_type.as_ref());
267
268 LayerEvaluationV2 {
269 base: result_to_base_eval_v2(layer_name, result, hashing),
270 value: get_json_value(result),
271 is_device_based,
272 group_name: result.group_name.take(),
273 is_experiment_active,
274 is_user_in_experiment,
275 allocated_experiment_name,
276 explicit_parameters: result.explicit_parameters.cloned().unwrap_or_default(),
277 undelegated_secondary_exposures: Some(undelegated_secondary_exposures),
278 id_type: Some(id_type),
279 }
280}
281
282pub fn result_to_layer_eval_init_v2(
283 result: &mut EvaluatorResult,
284 expo_id_to_exposure_map: &mut HashMap<InternedString, SecondaryExposure>,
285 expo_key_to_expo_id_map: &mut AHashMap<SecondaryExposureKey, InternedString>,
286 value_id_to_value_map: &mut HashMap<InternedString, DynamicReturnable>,
287 value_key_to_value_id: &mut AHashMap<u64, InternedString>,
288) -> LayerEvaluationInitV2 {
289 let value: Option<DynamicReturnable> = result.json_value.take();
290
291 let mut allocated_experiment_name = None;
292 let mut is_experiment_active = None;
293 let mut is_user_in_experiment = None;
294
295 if let Some(config_delegate) = result.config_delegate.take() {
296 if !config_delegate.is_empty() {
297 allocated_experiment_name = Some(config_delegate.clone());
298 is_experiment_active = Some(result.is_experiment_active);
299 is_user_in_experiment = Some(result.is_experiment_group);
300 }
301 }
302
303 let id_type = result.id_type.take();
304
305 let mapped_exposures: Option<Vec<InternedString>> = match result
306 .undelegated_secondary_exposures
307 .as_mut()
308 {
309 Some(undelegated_secondary_exposures) if !&undelegated_secondary_exposures.is_empty() => {
310 Some(self::map_exposures(
311 undelegated_secondary_exposures,
312 expo_id_to_exposure_map,
313 expo_key_to_expo_id_map,
314 ))
315 }
316 _ => None,
317 };
318
319 let mapped_value = self::get_mapped_value(value, value_id_to_value_map, value_key_to_value_id);
320
321 LayerEvaluationInitV2 {
322 base: result_to_base_eval_init_v2(result, expo_id_to_exposure_map, expo_key_to_expo_id_map),
323 id_type,
324 value: mapped_value,
325 group_name: result.group_name.take(),
326 is_experiment_active,
327 is_user_in_experiment,
328 allocated_experiment_name,
329 explicit_parameters: result.explicit_parameters.cloned(),
330 undelegated_secondary_exposures: mapped_exposures,
331 }
332}
333
334pub fn result_to_dynamic_config_eval(
335 dynamic_config_name: &str,
336 result: &mut EvaluatorResult,
337) -> DynamicConfigEvaluation {
338 let (id_type, is_device_based) = get_id_type_info(result.id_type.as_ref());
339
340 DynamicConfigEvaluation {
341 base: result_to_base_eval(dynamic_config_name, result),
342 id_type: Some(id_type),
343 is_device_based,
344 value: get_json_value(result),
345 passed: result.bool_value,
346 }
347}
348
349pub fn result_to_dynamic_config_eval_v2(
350 dynamic_config_name: &str,
351 result: &mut EvaluatorResult,
352 hashing: &HashUtil,
353) -> DynamicConfigEvaluationV2 {
354 let (id_type, is_device_based) = get_id_type_info(result.id_type.as_ref());
355
356 DynamicConfigEvaluationV2 {
357 base: result_to_base_eval_v2(dynamic_config_name, result, hashing),
358 id_type: Some(id_type),
359 is_device_based,
360 value: get_json_value(result),
361 passed: result.bool_value,
362 }
363}
364
365pub fn result_to_dynamic_config_eval_init_v2(
366 result: &mut EvaluatorResult,
367 expo_id_to_exposure_map: &mut HashMap<InternedString, SecondaryExposure>,
368 expo_key_to_expo_id_map: &mut AHashMap<SecondaryExposureKey, InternedString>,
369 value_id_to_value_map: &mut HashMap<InternedString, DynamicReturnable>,
370 value_key_to_value_id: &mut AHashMap<u64, InternedString>,
371) -> DynamicConfigEvaluationInitV2 {
372 let value: Option<DynamicReturnable> = result.json_value.take();
373
374 let id_type = result.id_type.take();
375
376 let mapped_value = self::get_mapped_value(value, value_id_to_value_map, value_key_to_value_id);
377
378 DynamicConfigEvaluationInitV2 {
379 base: result_to_base_eval_init_v2(result, expo_id_to_exposure_map, expo_key_to_expo_id_map),
380 id_type,
381 value: mapped_value,
382 passed: result.bool_value,
383 }
384}
385
386fn get_mapped_value(
387 value: Option<DynamicReturnable>,
388 value_id_to_value_map: &mut HashMap<InternedString, DynamicReturnable>,
389 value_key_to_value_id: &mut AHashMap<u64, InternedString>,
390) -> InternedString {
391 let val = match &value {
392 Some(v) => v,
393 None => &DynamicReturnable::empty(),
394 };
395 let hash = val.get_hash();
396 match value_key_to_value_id.get(&hash) {
397 Some(mapped_key) => mapped_key.clone(),
398 None => {
399 let value_id = InternedString::from_string(value_id_to_value_map.len().to_string());
400 value_id_to_value_map.insert(value_id.clone(), val.clone());
401 value_key_to_value_id.insert(hash, value_id.clone());
402 value_id
403 }
404 }
405}
406
407fn get_id_type_info(id_type: Option<&InternedString>) -> (InternedString, bool) {
408 let id_type = id_type.cloned().unwrap_or_default();
409 let is_device_based = id_type == "stableID" || id_type == "stableid";
410 (id_type, is_device_based)
411}
412
413fn get_json_value(result: &mut EvaluatorResult) -> DynamicReturnable {
414 result
415 .json_value
416 .take()
417 .unwrap_or_else(DynamicReturnable::empty)
418}
419
420fn get_exposure_name_if_not_hashed(
422 possibly_hashed_name: &str,
423 exposure_name: Option<&InternedString>,
424) -> InternedString {
425 let exposure_name = exposure_name.unwrap_or(InternedString::empty_ref());
426 if possibly_hashed_name == exposure_name.as_str() {
427 exposure_name.clone()
428 } else {
429 InternedString::from_str_ref(possibly_hashed_name)
430 }
431}
432
433fn result_to_base_eval(spec_name: &str, result: &mut EvaluatorResult) -> BaseEvaluation {
434 let rule_id = create_suffixed_rule_id(result.rule_id, result.rule_id_suffix);
435
436 let exposure_info = ExtraExposureInfo {
437 sampling_rate: result.sampling_rate,
438 forward_all_exposures: result.forward_all_exposures,
439 has_seen_analytical_gates: result.has_seen_analytical_gates,
440 override_config_name: result.override_config_name.map(|s| s.to_string()),
441 version: result.version,
442 };
443
444 let name = get_exposure_name_if_not_hashed(spec_name, result.name);
445
446 BaseEvaluation {
447 name,
448 rule_id,
449 secondary_exposures: std::mem::take(&mut result.secondary_exposures),
450 exposure_info: Some(exposure_info),
451 }
452}
453
454fn result_to_base_eval_v2(
455 spec_name: &str,
456 result: &mut EvaluatorResult,
457 hashing: &HashUtil,
458) -> BaseEvaluationV2 {
459 let mut exposures = Vec::new();
460
461 for exposure in &result.secondary_exposures {
462 let key = format!(
463 "{}:{}:{}",
464 exposure.gate,
465 exposure.gate_value,
466 exposure.rule_id.as_str()
467 );
468 let hash = hashing.hash(&key, &HashAlgorithm::Djb2);
469 exposures.push(hash.clone());
470 }
471
472 let rule_id = create_suffixed_rule_id(result.rule_id, result.rule_id_suffix);
473
474 BaseEvaluationV2 {
475 name: spec_name.to_string(),
476 rule_id,
477 secondary_exposures: exposures,
478 }
479}
480
481fn result_to_base_eval_init_v2(
482 result: &mut EvaluatorResult,
483 expo_id_to_exposure_map: &mut HashMap<InternedString, SecondaryExposure>,
484 expo_key_to_expo_id_map: &mut AHashMap<SecondaryExposureKey, InternedString>,
485) -> BaseEvaluationInitV2 {
486 let rule_id = create_suffixed_rule_id(result.rule_id, result.rule_id_suffix);
487 let opt_rule_id = match rule_id.as_str() {
488 "default" => None,
489 _ => Some(rule_id),
490 };
491
492 if result.secondary_exposures.is_empty() {
493 return BaseEvaluationInitV2 {
494 rule_id: opt_rule_id,
495 secondary_exposures: None,
496 };
497 }
498
499 BaseEvaluationInitV2 {
500 rule_id: opt_rule_id,
501 secondary_exposures: Some(self::map_exposures(
502 &mut result.secondary_exposures,
503 expo_id_to_exposure_map,
504 expo_key_to_expo_id_map,
505 )),
506 }
507}
508
509fn map_exposures(
510 input_exposures: &mut Vec<SecondaryExposure>,
511 expo_id_to_exposure_map: &mut HashMap<InternedString, SecondaryExposure>,
512 expo_key_to_expo_id_map: &mut AHashMap<SecondaryExposureKey, InternedString>,
513) -> Vec<InternedString> {
514 let my_exposures = std::mem::take(input_exposures);
515 my_exposures
516 .into_iter()
517 .map(|exposure| {
518 let expo_key = SecondaryExposureKey::from(&exposure);
519
520 match expo_key_to_expo_id_map.get(&expo_key) {
521 Some(expo_id) => expo_id.clone(),
522 None => {
523 let expo_id =
524 InternedString::from_string(expo_id_to_exposure_map.len().to_string());
525
526 expo_id_to_exposure_map.insert(expo_id.clone(), exposure);
527 expo_key_to_expo_id_map.insert(expo_key, expo_id.clone());
528
529 expo_id
530 }
531 }
532 })
533 .collect()
534}
535
536fn create_suffixed_rule_id(
537 rule_id: Option<&InternedString>,
538 suffix: Option<&str>,
539) -> InternedString {
540 let id_arc = match &rule_id {
541 Some(rule_id) => rule_id.as_str(),
542 None => "",
543 };
544
545 match &suffix {
546 Some(suffix) => InternedString::from_str_parts(&[id_arc, ":", suffix]),
547 None => rule_id.cloned().unwrap_or_default(),
548 }
549}