statsig_rust/
statsig_types.rs

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