statsig_rust/evaluation/
evaluation_types.rs

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