statsig_rust/evaluation/
evaluation_types.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5use crate::event_logging::exposable_string::ExposableString;
6
7pub fn is_false(v: &bool) -> bool {
8    !(*v)
9}
10
11#[derive(Serialize, Deserialize, Clone, Debug)]
12#[serde(rename_all = "camelCase")]
13pub struct SecondaryExposure {
14    pub gate: String,
15    pub gate_value: String,
16    #[serde(rename = "ruleID")]
17    pub rule_id: ExposableString,
18}
19
20impl SecondaryExposure {
21    pub fn get_dedupe_key(&self) -> String {
22        let mut key = String::new();
23        key += &self.gate;
24        key += "|";
25        key += &self.gate_value;
26        key += "|";
27        key += self.rule_id.as_str();
28        key
29    }
30}
31
32#[derive(Serialize, Deserialize, Clone, Debug, Default)]
33pub struct ExtraExposureInfo {
34    pub sampling_rate: Option<u64>,
35    pub forward_all_exposures: Option<bool>,
36    pub has_seen_analytical_gates: Option<bool>,
37    pub override_config_name: Option<String>,
38    pub version: Option<u32>,
39}
40
41#[derive(Serialize, Deserialize, Clone)]
42pub struct BaseEvaluation {
43    pub name: ExposableString,
44    pub rule_id: ExposableString,
45    pub secondary_exposures: Vec<SecondaryExposure>,
46
47    #[serde(skip_serializing)]
48    pub(crate) exposure_info: Option<ExtraExposureInfo>,
49}
50
51pub enum AnyEvaluation<'a> {
52    FeatureGate(&'a GateEvaluation),
53    DynamicConfig(&'a DynamicConfigEvaluation),
54    Experiment(&'a ExperimentEvaluation),
55    Layer(&'a LayerEvaluation),
56}
57
58impl AnyEvaluation<'_> {
59    pub fn get_base_result(&self) -> &BaseEvaluation {
60        match self {
61            AnyEvaluation::FeatureGate(gate) => &gate.base,
62            AnyEvaluation::DynamicConfig(config) => &config.base,
63            AnyEvaluation::Experiment(experiment) => &experiment.base,
64            AnyEvaluation::Layer(layer) => &layer.base,
65        }
66    }
67
68    pub fn get_gate_bool_value(&self) -> bool {
69        match self {
70            AnyEvaluation::FeatureGate(eval) => eval.value,
71            _ => false, // return false for all other types
72        }
73    }
74}
75
76impl<'a> From<&'a LayerEvaluation> for AnyEvaluation<'a> {
77    fn from(layer_eval: &'a LayerEvaluation) -> Self {
78        AnyEvaluation::Layer(layer_eval)
79    }
80}
81
82impl<'a> From<&'a GateEvaluation> for AnyEvaluation<'a> {
83    fn from(gate_eval: &'a GateEvaluation) -> Self {
84        AnyEvaluation::FeatureGate(gate_eval)
85    }
86}
87
88impl<'a> From<&'a ExperimentEvaluation> for AnyEvaluation<'a> {
89    fn from(experiment_eval: &'a ExperimentEvaluation) -> Self {
90        AnyEvaluation::Experiment(experiment_eval)
91    }
92}
93
94impl<'a> From<&'a DynamicConfigEvaluation> for AnyEvaluation<'a> {
95    fn from(dynamic_config_evalation: &'a DynamicConfigEvaluation) -> Self {
96        AnyEvaluation::DynamicConfig(dynamic_config_evalation)
97    }
98}
99
100#[derive(Serialize, Deserialize, Clone)]
101pub struct GateEvaluation {
102    #[serde(flatten)]
103    pub base: BaseEvaluation,
104
105    pub id_type: String,
106    pub value: bool,
107}
108
109#[derive(Serialize, Deserialize, Clone)]
110pub struct DynamicConfigEvaluation {
111    #[serde(flatten)]
112    pub base: BaseEvaluation,
113
114    pub id_type: String,
115    pub value: HashMap<String, Value>,
116
117    // The 'group' field is identical to 'rule_id'. See group_name instead.
118    pub group: ExposableString,
119    pub is_device_based: bool,
120
121    pub passed: bool,
122}
123
124#[derive(Serialize, Deserialize, Clone)]
125pub struct ExperimentEvaluation {
126    #[serde(flatten)]
127    pub base: BaseEvaluation,
128
129    pub id_type: String,
130    pub value: HashMap<String, Value>,
131
132    // The 'group' field is identical to 'rule_id'. See group_name instead.
133    pub group: ExposableString,
134    pub is_device_based: bool,
135
136    #[serde(skip_serializing_if = "is_false")]
137    pub is_in_layer: bool,
138
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub explicit_parameters: Option<Vec<String>>,
141
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub group_name: Option<String>,
144
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub is_experiment_active: Option<bool>,
147
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub is_user_in_experiment: Option<bool>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
152}
153
154#[derive(Serialize, Deserialize, Clone)]
155#[serde(untagged)]
156pub enum AnyConfigEvaluation {
157    DynamicConfig(DynamicConfigEvaluation),
158    Experiment(ExperimentEvaluation),
159}
160
161#[derive(Serialize, Deserialize, Clone)]
162pub struct LayerEvaluation {
163    #[serde(flatten)]
164    pub base: BaseEvaluation,
165
166    pub value: HashMap<String, Value>,
167
168    pub id_type: String,
169
170    // The 'group' field is identical to 'rule_id'. See group_name instead.
171    pub group: String,
172    pub is_device_based: bool,
173
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub group_name: Option<String>,
176
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub is_experiment_active: Option<bool>,
179
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub is_user_in_experiment: Option<bool>,
182
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub allocated_experiment_name: Option<String>,
185    pub explicit_parameters: Vec<String>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
188}