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