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