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>, #[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}