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