statsig_rust/
statsig_types.rs

1use crate::evaluation::evaluation_details::EvaluationDetails;
2use crate::evaluation::evaluation_types::{
3    DynamicConfigEvaluation, ExperimentEvaluation, GateEvaluation, LayerEvaluation,
4};
5use crate::event_logging::event_logger::{EventLogger, ExposureTrigger};
6use crate::event_logging::event_queue::queued_layer_param_expo::EnqueueLayerParamExpoOp;
7use crate::specs_response::param_store_types::Parameter;
8use crate::statsig_core_api_options::ParameterStoreEvaluationOptions;
9use crate::user::StatsigUserLoggable;
10use crate::Statsig;
11use crate::StatsigUser;
12
13use chrono::Utc;
14use serde::de::DeserializeOwned;
15use serde::{Deserialize, Serialize};
16use serde_json::{from_value, Value};
17use std::collections::HashMap;
18use std::sync::Weak;
19
20#[derive(Serialize, Deserialize, Clone)]
21pub struct FeatureGate {
22    pub name: String,
23    pub value: bool,
24    pub rule_id: String,
25    pub id_type: String,
26    pub details: EvaluationDetails,
27
28    pub(crate) __evaluation: Option<GateEvaluation>,
29}
30
31#[derive(Serialize, Deserialize, Clone)]
32pub struct DynamicConfig {
33    pub name: String,
34    pub value: HashMap<String, Value>,
35    pub rule_id: String,
36    pub id_type: String,
37    pub details: EvaluationDetails,
38
39    pub __evaluation: Option<DynamicConfigEvaluation>,
40}
41
42impl DynamicConfig {
43    #[must_use]
44    pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
45        match self.value.get(param_name) {
46            Some(value) => from_value(value.clone()).ok(),
47            None => None,
48        }
49    }
50}
51
52#[derive(Serialize, Deserialize, Clone)]
53pub struct Experiment {
54    pub name: String,
55    pub value: HashMap<String, Value>,
56    pub rule_id: String,
57    pub id_type: String,
58    pub group_name: Option<String>,
59    pub details: EvaluationDetails,
60
61    pub __evaluation: Option<ExperimentEvaluation>,
62}
63
64impl Experiment {
65    #[must_use]
66    pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
67        match self.value.get(param_name) {
68            Some(value) => from_value(value.clone()).ok(),
69            None => None,
70        }
71    }
72}
73
74#[derive(Serialize, Deserialize, Clone)]
75pub struct Layer {
76    pub name: String,
77    pub rule_id: String,
78    pub id_type: String,
79
80    pub group_name: Option<String>,
81    pub details: EvaluationDetails,
82    pub allocated_experiment_name: Option<String>,
83
84    pub __evaluation: Option<LayerEvaluation>,
85    pub __value: HashMap<String, Value>,
86    pub __user: StatsigUserLoggable,
87    pub __disable_exposure: bool,
88
89    pub __version: Option<u32>, // todo: rm when Java/PHP layer exposures are not a JSON round trip
90
91    #[serde(skip_serializing, skip_deserializing)]
92    pub __event_logger_ptr: Option<Weak<EventLogger>>,
93}
94
95impl Layer {
96    pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
97        let value = match self.__value.get(param_name) {
98            Some(value) => value.clone(),
99            None => return None,
100        };
101
102        match from_value(value.clone()) {
103            Ok(value) => {
104                self.log_param_exposure(param_name);
105                Some(value)
106            }
107            Err(_) => None,
108        }
109    }
110
111    pub fn get_raw_value(&self, param_name: &str) -> Option<Value> {
112        match self.__value.get(param_name) {
113            Some(value) => {
114                self.log_param_exposure(param_name);
115                Some(value.clone())
116            }
117            None => None,
118        }
119    }
120
121    fn log_param_exposure(&self, param_name: &str) -> Option<()> {
122        let logger = self.__event_logger_ptr.as_ref()?.upgrade()?;
123
124        if self.__disable_exposure {
125            logger.increment_non_exposure_checks(&self.name);
126            return None;
127        }
128
129        logger.enqueue(EnqueueLayerParamExpoOp::LayerRef(
130            Utc::now().timestamp_millis() as u64,
131            self,
132            param_name,
133            ExposureTrigger::Auto,
134        ));
135
136        None
137    }
138}
139
140macro_rules! impl_common_get_methods {
141    ($struct_name:ident) => {
142        impl $struct_name {
143            pub fn get<T: DeserializeOwned>(&self, param_name: &str, fallback: T) -> T {
144                self.get_opt(param_name).unwrap_or_else(|| fallback)
145            }
146
147            #[must_use]
148            pub fn get_bool(&self, param_name: &str, fallback: bool) -> bool {
149                self.get(param_name, fallback)
150            }
151
152            #[must_use]
153            pub fn get_f64(&self, param_name: &str, fallback: f64) -> f64 {
154                self.get(param_name, fallback)
155            }
156
157            #[must_use]
158            pub fn get_i64(&self, param_name: &str, fallback: i64) -> i64 {
159                self.get(param_name, fallback)
160            }
161
162            #[must_use]
163            pub fn get_string(&self, param_name: &str, fallback: String) -> String {
164                self.get(param_name, fallback)
165            }
166
167            #[must_use]
168            pub fn get_array(&self, param_name: &str, fallback: Vec<Value>) -> Vec<Value> {
169                self.get(param_name, fallback)
170            }
171
172            #[must_use]
173            pub fn get_object(
174                &self,
175                param_name: &str,
176                fallback: HashMap<String, Value>,
177            ) -> HashMap<String, Value> {
178                self.get(param_name, fallback)
179            }
180        }
181    };
182}
183
184#[derive(Serialize, Clone)]
185pub struct ParameterStore<'a> {
186    pub name: String,
187    pub details: EvaluationDetails,
188    pub parameters: HashMap<String, Parameter>,
189    pub options: ParameterStoreEvaluationOptions,
190
191    #[serde(skip_serializing, skip_deserializing)]
192    pub _statsig_ref: &'a Statsig,
193}
194
195impl ParameterStore<'_> {
196    pub fn get_opt<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str) -> Option<T> {
197        let param = self.parameters.get(param_name)?;
198        match param {
199            Parameter::StaticValue(static_value) => from_value(static_value.value.clone()).ok(),
200            Parameter::Gate(gate) => {
201                let res = self._statsig_ref.check_gate_with_options(
202                    user,
203                    &gate.gate_name,
204                    self.options.into(),
205                );
206                let val = match res {
207                    true => gate.pass_value.clone(),
208                    false => gate.fail_value.clone(),
209                };
210                from_value(val).ok()
211            }
212            Parameter::DynamicConfig(dynamic_config) => {
213                let res = self._statsig_ref.get_dynamic_config_with_options(
214                    user,
215                    &dynamic_config.config_name,
216                    self.options.into(),
217                );
218                res.get_opt(&dynamic_config.param_name)?
219            }
220            Parameter::Experiment(experiment) => {
221                let res = self._statsig_ref.get_experiment_with_options(
222                    user,
223                    &experiment.experiment_name,
224                    self.options.into(),
225                );
226                res.get_opt(&experiment.param_name)?
227            }
228            Parameter::Layer(layer) => {
229                let res = self._statsig_ref.get_layer_with_options(
230                    user,
231                    &layer.layer_name,
232                    self.options.into(),
233                );
234                res.get_opt(&layer.param_name)?
235            }
236        }
237    }
238
239    pub fn get<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str, fallback: T) -> T {
240        self.get_opt(user, param_name).unwrap_or(fallback)
241    }
242
243    pub fn get_json_value(
244        &self,
245        user: &StatsigUser,
246        param_name: &str,
247        fallback: Option<Value>,
248    ) -> Value {
249        match fallback {
250            None | Some(Value::Null) => self
251                .get_opt::<Value>(user, param_name)
252                .unwrap_or(Value::Null),
253            Some(Value::Bool(boolean)) => self.get_bool(user, param_name, boolean).into(),
254            Some(Value::Number(number)) => self.get(user, param_name, number).into(),
255            Some(Value::String(string)) => self.get_string(user, param_name, string).into(),
256            Some(Value::Array(vec)) => self.get_array(user, param_name, vec).into(),
257            Some(Value::Object(map)) => self
258                .get_object(user, param_name, map.into_iter().collect())
259                .into_iter()
260                .collect(),
261        }
262    }
263
264    pub fn get_bool(&self, user: &StatsigUser, param_name: &str, fallback: bool) -> bool {
265        self.get(user, param_name, fallback)
266    }
267
268    pub fn get_f64(&self, user: &StatsigUser, param_name: &str, fallback: f64) -> f64 {
269        self.get(user, param_name, fallback)
270    }
271
272    pub fn get_i64(&self, user: &StatsigUser, param_name: &str, fallback: i64) -> i64 {
273        self.get(user, param_name, fallback)
274    }
275
276    pub fn get_string(&self, user: &StatsigUser, param_name: &str, fallback: String) -> String {
277        self.get(user, param_name, fallback)
278    }
279
280    pub fn get_array(
281        &self,
282        user: &StatsigUser,
283        param_name: &str,
284        fallback: Vec<Value>,
285    ) -> Vec<Value> {
286        self.get(user, param_name, fallback)
287    }
288
289    pub fn get_object(
290        &self,
291        user: &StatsigUser,
292        param_name: &str,
293        fallback: HashMap<String, Value>,
294    ) -> HashMap<String, Value> {
295        self.get(user, param_name, fallback)
296    }
297}
298
299impl_common_get_methods!(DynamicConfig);
300impl_common_get_methods!(Experiment);
301impl_common_get_methods!(Layer);
302
303pub enum OverrideAdapterType {
304    LocalOverride,
305}