1use std::collections::HashMap;
2
3use super::dynamic_returnable::DynamicReturnable;
4use crate::{
5 evaluation::secondary_exposure_key::SecondaryExposureKey,
6 gcir::gcir_formatter::GCIRHashable,
7 hashing::{self, opt_bool_to_hashable},
8 interned_string::InternedString,
9 specs_response::explicit_params::ExplicitParameters,
10};
11
12use serde::{Deserialize, Serialize};
13
14pub fn is_false(v: &bool) -> bool {
15 !(*v)
16}
17
18#[derive(Serialize, Deserialize, Clone, Debug)]
19#[serde(rename_all = "camelCase")]
20pub struct SecondaryExposure {
21 pub gate: InternedString,
22 pub gate_value: InternedString,
23 #[serde(rename = "ruleID")]
24 pub rule_id: InternedString,
25}
26
27impl GCIRHashable for SecondaryExposure {
28 fn create_hash(&self, name: &InternedString) -> u64 {
29 let hash_array = vec![name.hash, self.gate_value.hash, self.rule_id.hash];
30 hashing::hash_one(hash_array)
31 }
32}
33
34impl SecondaryExposure {
35 pub fn get_dedupe_key(&self) -> String {
36 let mut key = String::new();
37 key += &self.gate;
38 key += "|";
39 key += &self.gate_value;
40 key += "|";
41 key += self.rule_id.as_str();
42 key
43 }
44}
45
46impl From<&SecondaryExposure> for SecondaryExposureKey {
47 fn from(val: &SecondaryExposure) -> Self {
48 SecondaryExposureKey {
49 gate_name_hash: val.gate.hash,
50 rule_id_hash: val.rule_id.hash,
51 gate_value_hash: val.gate_value.hash,
52 }
53 }
54}
55
56#[derive(Serialize, Deserialize, Clone, Debug, Default)]
57pub struct ExtraExposureInfo {
58 pub sampling_rate: Option<u64>,
59 pub forward_all_exposures: Option<bool>,
60 pub has_seen_analytical_gates: Option<bool>,
61 pub override_config_name: Option<InternedString>,
62 pub version: Option<u32>,
63}
64
65#[derive(Serialize, Deserialize, Clone)]
66pub struct BaseEvaluation {
67 pub name: InternedString,
68 pub rule_id: InternedString,
69 pub secondary_exposures: Vec<SecondaryExposure>,
70
71 #[serde(skip_serializing)]
72 pub(crate) exposure_info: Option<ExtraExposureInfo>,
73}
74
75pub enum AnyEvaluation<'a> {
76 FeatureGate(&'a GateEvaluation),
77 DynamicConfig(&'a DynamicConfigEvaluation),
78 Experiment(&'a ExperimentEvaluation),
79 Layer(&'a LayerEvaluation),
80}
81
82impl AnyEvaluation<'_> {
83 pub fn get_base_result(&self) -> &BaseEvaluation {
84 match self {
85 AnyEvaluation::FeatureGate(gate) => &gate.base,
86 AnyEvaluation::DynamicConfig(config) => &config.base,
87 AnyEvaluation::Experiment(experiment) => &experiment.base,
88 AnyEvaluation::Layer(layer) => &layer.base,
89 }
90 }
91
92 pub fn get_gate_bool_value(&self) -> bool {
93 match self {
94 AnyEvaluation::FeatureGate(eval) => eval.value,
95 _ => false, }
97 }
98}
99
100impl<'a> From<&'a LayerEvaluation> for AnyEvaluation<'a> {
101 fn from(layer_eval: &'a LayerEvaluation) -> Self {
102 AnyEvaluation::Layer(layer_eval)
103 }
104}
105
106impl<'a> From<&'a GateEvaluation> for AnyEvaluation<'a> {
107 fn from(gate_eval: &'a GateEvaluation) -> Self {
108 AnyEvaluation::FeatureGate(gate_eval)
109 }
110}
111
112impl<'a> From<&'a ExperimentEvaluation> for AnyEvaluation<'a> {
113 fn from(experiment_eval: &'a ExperimentEvaluation) -> Self {
114 AnyEvaluation::Experiment(experiment_eval)
115 }
116}
117
118impl<'a> From<&'a DynamicConfigEvaluation> for AnyEvaluation<'a> {
119 fn from(dynamic_config_evalation: &'a DynamicConfigEvaluation) -> Self {
120 AnyEvaluation::DynamicConfig(dynamic_config_evalation)
121 }
122}
123
124#[derive(Serialize, Deserialize, Clone)]
125pub struct GateEvaluation {
126 #[serde(flatten)]
127 pub base: BaseEvaluation,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub id_type: Option<InternedString>,
131 pub value: bool,
132}
133
134impl GCIRHashable for GateEvaluation {
135 fn create_hash(&self, name: &InternedString) -> u64 {
136 let hash_array = vec![
137 name.hash,
138 self.value as u64,
139 self.base.rule_id.hash,
140 hash_secondary_exposures(&self.base.secondary_exposures),
141 ];
142 hashing::hash_one(hash_array)
143 }
144}
145
146#[derive(Serialize, Deserialize, Clone)]
147pub struct DynamicConfigEvaluation {
148 #[serde(flatten)]
149 pub base: BaseEvaluation,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub id_type: Option<InternedString>,
153 pub value: DynamicReturnable,
154
155 pub is_device_based: bool,
156
157 pub passed: bool,
158}
159
160impl GCIRHashable for DynamicConfigEvaluation {
161 fn create_hash(&self, name: &InternedString) -> u64 {
162 let hash_array = vec![
163 name.hash,
164 self.value.get_hash(),
165 self.base.rule_id.hash,
166 hash_secondary_exposures(&self.base.secondary_exposures),
167 self.passed as u64,
168 ];
169 hashing::hash_one(hash_array)
170 }
171}
172
173#[derive(Serialize, Deserialize, Clone)]
174pub struct ExperimentEvaluation {
175 #[serde(flatten)]
176 pub base: BaseEvaluation,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub id_type: Option<InternedString>,
180 pub value: DynamicReturnable,
181
182 pub is_device_based: bool,
183
184 #[serde(skip_serializing_if = "is_false")]
185 pub is_in_layer: bool,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub explicit_parameters: Option<ExplicitParameters>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub group_name: Option<InternedString>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub is_experiment_active: Option<bool>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub is_user_in_experiment: Option<bool>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
200}
201
202impl GCIRHashable for ExperimentEvaluation {
203 fn create_hash(&self, name: &InternedString) -> u64 {
204 let mut hash_array = vec![
205 name.hash,
206 self.value.get_hash(),
207 self.base.rule_id.hash,
208 hash_secondary_exposures(&self.base.secondary_exposures),
209 self.is_in_layer as u64,
210 ];
211 let mut explicit_params_hashes = Vec::new();
212 if let Some(explicit_parameters) = &self.explicit_parameters {
213 for value in explicit_parameters.to_vec_interned() {
214 explicit_params_hashes.push(value.hash);
215 }
216 }
217 hash_array.push(hashing::hash_one(explicit_params_hashes));
218 hash_array.push(self.group_name.as_ref().map_or(0, |g| g.hash));
219 hash_array.push(opt_bool_to_hashable(&self.is_experiment_active));
220 hash_array.push(opt_bool_to_hashable(&self.is_user_in_experiment));
221
222 hashing::hash_one(hash_array)
223 }
224}
225
226#[derive(Serialize, Deserialize, Clone)]
227#[serde(untagged)]
228pub enum AnyConfigEvaluation {
229 DynamicConfig(DynamicConfigEvaluation),
230 Experiment(ExperimentEvaluation),
231}
232
233impl GCIRHashable for AnyConfigEvaluation {
234 fn create_hash(&self, name: &InternedString) -> u64 {
235 match self {
236 AnyConfigEvaluation::DynamicConfig(eval) => eval.create_hash(name),
237 AnyConfigEvaluation::Experiment(eval) => eval.create_hash(name),
238 }
239 }
240}
241
242#[derive(Serialize, Deserialize, Clone)]
243pub struct LayerEvaluation {
244 #[serde(flatten)]
245 pub base: BaseEvaluation,
246
247 pub value: DynamicReturnable,
248
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub id_type: Option<InternedString>,
251
252 pub is_device_based: bool,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub group_name: Option<InternedString>,
256
257 #[serde(skip_serializing_if = "Option::is_none")]
258 pub is_experiment_active: Option<bool>,
259
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub is_user_in_experiment: Option<bool>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub allocated_experiment_name: Option<InternedString>,
265 pub explicit_parameters: ExplicitParameters,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
268
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub parameter_rule_ids: Option<HashMap<InternedString, InternedString>>,
271}
272
273impl GCIRHashable for LayerEvaluation {
274 fn create_hash(&self, name: &InternedString) -> u64 {
275 let mut hash_array = vec![
276 name.hash,
277 self.value.get_hash(),
278 self.base.rule_id.hash,
279 hash_secondary_exposures(&self.base.secondary_exposures),
280 self.group_name.as_ref().map_or(0, |g| g.hash),
281 opt_bool_to_hashable(&self.is_experiment_active),
282 opt_bool_to_hashable(&self.is_user_in_experiment),
283 self.allocated_experiment_name
284 .as_ref()
285 .map_or(0, |n| n.hash),
286 ];
287 let mut explicit_params_hashes = Vec::new();
288 for value in self.explicit_parameters.to_vec_interned() {
289 explicit_params_hashes.push(value.hash);
290 }
291 hash_array.push(hashing::hash_one(explicit_params_hashes));
292 let mut undelegated_secondary_exposure_hashes = Vec::new();
293 if let Some(undelegated_secondary_exposures) = &self.undelegated_secondary_exposures {
294 for exposure in undelegated_secondary_exposures {
295 undelegated_secondary_exposure_hashes.push(exposure.create_hash(&exposure.gate));
296 }
297 }
298 hash_array.push(hashing::hash_one(undelegated_secondary_exposure_hashes));
299 if let Some(parameter_rule_ids) = &self.parameter_rule_ids {
300 let mut param_rule_ids_hash = Vec::new();
301 for (param_name, rule_id) in parameter_rule_ids {
302 param_rule_ids_hash.push(param_name.hash);
303 param_rule_ids_hash.push(rule_id.hash);
304 }
305 hash_array.push(hashing::hash_one(param_rule_ids_hash));
306 }
307
308 hashing::hash_one(hash_array)
309 }
310}
311
312fn hash_secondary_exposures(exposures: &Vec<SecondaryExposure>) -> u64 {
313 let mut secondary_exposure_hashes = Vec::new();
314 for exposure in exposures {
315 secondary_exposure_hashes.push(exposure.create_hash(&exposure.gate));
316 }
317 hashing::hash_one(secondary_exposure_hashes)
318}