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_user_internal::StatsigUserInternal;
10use crate::StatsigUser;
11use crate::{SamplingProcessor, Statsig};
12use serde::de::DeserializeOwned;
13use serde::{Deserialize, Serialize};
14use serde_json::{from_value, Value};
15use std::collections::HashMap;
16use std::sync::Weak;
17
18#[derive(Serialize, Deserialize, Clone)]
19pub struct FeatureGate {
20    pub name: String,
21    pub value: bool,
22    pub rule_id: String,
23    pub id_type: String,
24    pub details: EvaluationDetails,
25
26    pub(crate) __evaluation: Option<GateEvaluation>,
27    pub __version: Option<u32>,
28    pub __override_config_name: Option<String>,
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    pub __version: Option<u32>,
41    pub __override_config_name: Option<String>,
42}
43
44impl DynamicConfig {
45    #[must_use]
46    pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
47        match self.value.get(param_name) {
48            Some(value) => from_value(value.clone()).ok(),
49            None => None,
50        }
51    }
52}
53
54#[derive(Serialize, Deserialize, Clone)]
55pub struct Experiment {
56    pub name: String,
57    pub value: HashMap<String, Value>,
58    pub rule_id: String,
59    pub id_type: String,
60    pub group_name: Option<String>,
61    pub details: EvaluationDetails,
62
63    pub __evaluation: Option<ExperimentEvaluation>,
64    pub __version: Option<u32>,
65    pub __override_config_name: Option<String>,
66}
67
68impl Experiment {
69    #[must_use]
70    pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
71        match self.value.get(param_name) {
72            Some(value) => from_value(value.clone()).ok(),
73            None => None,
74        }
75    }
76}
77
78#[derive(Serialize, Deserialize, Clone)]
79pub struct Layer {
80    pub name: String,
81    pub rule_id: String,
82
83    pub group_name: Option<String>,
84    pub details: EvaluationDetails,
85    pub allocated_experiment_name: Option<String>,
86
87    pub __evaluation: Option<LayerEvaluation>,
88    pub __value: HashMap<String, Value>,
89    pub __user: StatsigUserInternal,
90    pub __version: Option<u32>,
91    pub __disable_exposure: bool,
92    pub __override_config_name: Option<String>,
93
94    #[serde(skip_serializing, skip_deserializing)]
95    pub __event_logger_ptr: Option<Weak<EventLogger>>,
96    #[serde(skip_serializing, skip_deserializing)]
97    pub __sampling_processor: Option<Weak<SamplingProcessor>>,
98}
99
100impl Layer {
101    pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
102        let value = match self.__value.get(param_name) {
103            Some(value) => value.clone(),
104            None => return None,
105        };
106
107        match from_value(value.clone()) {
108            Ok(value) => {
109                self.log_param_exposure(param_name);
110                Some(value)
111            }
112            Err(_) => None,
113        }
114    }
115
116    pub fn get_raw_value(&self, param_name: &str) -> Option<Value> {
117        match self.__value.get(param_name) {
118            Some(value) => {
119                self.log_param_exposure(param_name);
120                Some(value.clone())
121            }
122            None => None,
123        }
124    }
125
126    fn log_param_exposure(&self, param_name: &str) -> Option<()> {
127        if self.__disable_exposure {
128            if let Some(ptr) = &self.__event_logger_ptr {
129                ptr.upgrade()?
130                    .increment_non_exposure_checks_count(self.name.clone());
131            }
132            return None;
133        }
134
135        let mut sampling_details = SamplingDecision::default();
136
137        if let Some(ptr) = &self.__sampling_processor {
138            let layer_eval = self.__evaluation.as_ref();
139
140            sampling_details = ptr.upgrade()?.get_sampling_decision_and_details(
141                &self.__user,
142                layer_eval.map(AnyEvaluation::from).as_ref(),
143                Some(param_name),
144            );
145
146            if !sampling_details.should_send_exposure {
147                return None;
148            }
149        }
150
151        if let Some(ptr) = &self.__event_logger_ptr {
152            ptr.upgrade()?
153                .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
154                    user: self.__user.clone(),
155                    layer_name: self.name.clone(),
156                    parameter_name: param_name.to_string(),
157                    evaluation: self.__evaluation.clone(),
158                    evaluation_details: self.details.clone(),
159                    version: self.__version,
160                    is_manual_exposure: false,
161                    sampling_details,
162                    override_config_name: self.__override_config_name.clone(),
163                }));
164        }
165
166        None
167    }
168}
169
170macro_rules! impl_common_get_methods {
171    ($struct_name:ident) => {
172        impl $struct_name {
173            pub fn get<T: DeserializeOwned>(&self, param_name: &str, fallback: T) -> T {
174                self.get_opt(param_name).unwrap_or_else(|| fallback)
175            }
176
177            #[must_use]
178            pub fn get_bool(&self, param_name: &str, fallback: bool) -> bool {
179                self.get(param_name, fallback)
180            }
181
182            #[must_use]
183            pub fn get_f64(&self, param_name: &str, fallback: f64) -> f64 {
184                self.get(param_name, fallback)
185            }
186
187            #[must_use]
188            pub fn get_i64(&self, param_name: &str, fallback: i64) -> i64 {
189                self.get(param_name, fallback)
190            }
191
192            #[must_use]
193            pub fn get_string(&self, param_name: &str, fallback: String) -> String {
194                self.get(param_name, fallback)
195            }
196
197            #[must_use]
198            pub fn get_array(&self, param_name: &str, fallback: Vec<Value>) -> Vec<Value> {
199                self.get(param_name, fallback)
200            }
201
202            #[must_use]
203            pub fn get_object(
204                &self,
205                param_name: &str,
206                fallback: HashMap<String, Value>,
207            ) -> HashMap<String, Value> {
208                self.get(param_name, fallback)
209            }
210        }
211    };
212}
213
214#[derive(Serialize, Clone)]
215pub struct ParameterStore<'a> {
216    pub name: String,
217    pub details: EvaluationDetails,
218    pub parameters: HashMap<String, Parameter>,
219
220    #[serde(skip_serializing, skip_deserializing)]
221    pub _statsig_ref: &'a Statsig,
222}
223
224impl ParameterStore<'_> {
225    pub fn get_opt<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str) -> Option<T> {
226        let param = self.parameters.get(param_name)?;
227        match param {
228            Parameter::StaticValue(static_value) => match from_value(static_value.value.clone()) {
229                Ok(value) => Some(value),
230                Err(_) => None,
231            },
232            Parameter::Gate(gate) => {
233                let res = self._statsig_ref.check_gate(user, &gate.gate_name);
234                let val = match res {
235                    true => gate.pass_value.clone(),
236                    false => gate.fail_value.clone(),
237                };
238                match from_value(val) {
239                    Ok(value) => Some(value),
240                    Err(_) => None,
241                }
242            }
243            Parameter::DynamicConfig(dynamic_config) => {
244                let res = self
245                    ._statsig_ref
246                    .get_dynamic_config(user, &dynamic_config.config_name);
247                res.get_opt(&dynamic_config.param_name)?
248            }
249            Parameter::Experiment(experiment) => {
250                let res = self
251                    ._statsig_ref
252                    .get_experiment(user, &experiment.experiment_name);
253                res.get_opt(&experiment.param_name)?
254            }
255            Parameter::Layer(layer) => {
256                let res = self._statsig_ref.get_layer(user, &layer.layer_name);
257                res.get_opt(&layer.param_name)?
258            }
259        }
260    }
261
262    pub fn get<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str, fallback: T) -> T {
263        self.get_opt(user, param_name).unwrap_or(fallback)
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}