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 pub fn get_typed_opt(&self, param_name: &str, fallback: Option<Value>) -> Option<Value> {
52 extract_matching_type(&self.value, param_name, &fallback).or(fallback)
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 pub is_experiment_active: bool,
65
66 pub __evaluation: Option<ExperimentEvaluation>,
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 pub fn get_typed_opt(&self, param_name: &str, fallback: Option<Value>) -> Option<Value> {
79 extract_matching_type(&self.value, param_name, &fallback).or(fallback)
80 }
81}
82
83#[derive(Serialize, Deserialize, Clone)]
84pub struct Layer {
85 pub name: String,
86 pub rule_id: String,
87 pub id_type: String,
88
89 pub group_name: Option<String>,
90 pub details: EvaluationDetails,
91 pub allocated_experiment_name: Option<String>,
92 pub is_experiment_active: bool,
93
94 pub __evaluation: Option<LayerEvaluation>,
95 pub __value: HashMap<String, Value>,
96 pub __user: StatsigUserLoggable,
97 pub __disable_exposure: bool,
98
99 pub __version: Option<u32>, #[serde(skip_serializing, skip_deserializing)]
102 pub __event_logger_ptr: Option<Weak<EventLogger>>,
103}
104
105impl Layer {
106 pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
107 let value = match self.__value.get(param_name) {
108 Some(value) => value.clone(),
109 None => return None,
110 };
111
112 match from_value(value.clone()) {
113 Ok(value) => {
114 self.log_param_exposure(param_name);
115 Some(value)
116 }
117 Err(_) => None,
118 }
119 }
120
121 pub fn get_typed_opt(&self, param_name: &str, fallback: Option<Value>) -> Option<Value> {
122 match extract_matching_type(&self.__value, param_name, &fallback) {
123 Some(value) => {
124 self.log_param_exposure(param_name);
125 Some(value)
126 }
127 None => fallback,
128 }
129 }
130
131 pub fn get_raw_value(&self, param_name: &str) -> Option<Value> {
132 match self.__value.get(param_name) {
133 Some(value) => {
134 self.log_param_exposure(param_name);
135 Some(value.clone())
136 }
137 None => None,
138 }
139 }
140
141 fn log_param_exposure(&self, param_name: &str) -> Option<()> {
142 let logger = self.__event_logger_ptr.as_ref()?.upgrade()?;
143
144 if self.__disable_exposure {
145 logger.increment_non_exposure_checks(&self.name);
146 return None;
147 }
148
149 logger.enqueue(EnqueueLayerParamExpoOp::LayerRef(
150 Utc::now().timestamp_millis() as u64,
151 self,
152 param_name,
153 ExposureTrigger::Auto,
154 ));
155
156 None
157 }
158}
159
160macro_rules! impl_common_get_methods {
161 ($struct_name:ident) => {
162 impl $struct_name {
163 pub fn get<T: DeserializeOwned>(&self, param_name: &str, fallback: T) -> T {
164 self.get_opt(param_name).unwrap_or_else(|| fallback)
165 }
166
167 #[must_use]
168 pub fn get_bool(&self, param_name: &str, fallback: bool) -> bool {
169 self.get(param_name, fallback)
170 }
171
172 #[must_use]
173 pub fn get_f64(&self, param_name: &str, fallback: f64) -> f64 {
174 self.get(param_name, fallback)
175 }
176
177 #[must_use]
178 pub fn get_i64(&self, param_name: &str, fallback: i64) -> i64 {
179 self.get(param_name, fallback)
180 }
181
182 #[must_use]
183 pub fn get_string(&self, param_name: &str, fallback: String) -> String {
184 self.get(param_name, fallback)
185 }
186
187 #[must_use]
188 pub fn get_array(&self, param_name: &str, fallback: Vec<Value>) -> Vec<Value> {
189 self.get(param_name, fallback)
190 }
191
192 #[must_use]
193 pub fn get_object(
194 &self,
195 param_name: &str,
196 fallback: HashMap<String, Value>,
197 ) -> HashMap<String, Value> {
198 self.get(param_name, fallback)
199 }
200 }
201 };
202}
203
204#[derive(Serialize, Clone)]
205pub struct ParameterStore<'a> {
206 pub name: String,
207 pub details: EvaluationDetails,
208 pub parameters: HashMap<String, Parameter>,
209 pub options: ParameterStoreEvaluationOptions,
210
211 #[serde(skip_serializing, skip_deserializing)]
212 pub _statsig_ref: &'a Statsig,
213}
214
215impl ParameterStore<'_> {
216 pub fn get_opt<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str) -> Option<T> {
217 let param = self.parameters.get(param_name)?;
218 match param {
219 Parameter::StaticValue(static_value) => from_value(static_value.value.clone()).ok(),
220 Parameter::Gate(gate) => {
221 let res = self._statsig_ref.check_gate_with_options(
222 user,
223 &gate.gate_name,
224 self.options.into(),
225 );
226 let val = match res {
227 true => gate.pass_value.clone(),
228 false => gate.fail_value.clone(),
229 };
230 from_value(val).ok()
231 }
232 Parameter::DynamicConfig(dynamic_config) => {
233 let res = self._statsig_ref.get_dynamic_config_with_options(
234 user,
235 &dynamic_config.config_name,
236 self.options.into(),
237 );
238 res.get_opt(&dynamic_config.param_name)?
239 }
240 Parameter::Experiment(experiment) => {
241 let res = self._statsig_ref.get_experiment_with_options(
242 user,
243 &experiment.experiment_name,
244 self.options.into(),
245 );
246 res.get_opt(&experiment.param_name)?
247 }
248 Parameter::Layer(layer) => {
249 let res = self._statsig_ref.get_layer_with_options(
250 user,
251 &layer.layer_name,
252 self.options.into(),
253 );
254 res.get_opt(&layer.param_name)?
255 }
256 }
257 }
258
259 pub fn get<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str, fallback: T) -> T {
260 self.get_opt(user, param_name).unwrap_or(fallback)
261 }
262
263 pub fn get_json_value(
264 &self,
265 user: &StatsigUser,
266 param_name: &str,
267 fallback: Option<Value>,
268 ) -> Value {
269 match fallback {
270 None | Some(Value::Null) => self
271 .get_opt::<Value>(user, param_name)
272 .unwrap_or(Value::Null),
273 Some(Value::Bool(boolean)) => self.get_bool(user, param_name, boolean).into(),
274 Some(Value::Number(number)) => self.get(user, param_name, number).into(),
275 Some(Value::String(string)) => self.get_string(user, param_name, string).into(),
276 Some(Value::Array(vec)) => self.get_array(user, param_name, vec).into(),
277 Some(Value::Object(map)) => self
278 .get_object(user, param_name, map.into_iter().collect())
279 .into_iter()
280 .collect(),
281 }
282 }
283
284 pub fn get_bool(&self, user: &StatsigUser, param_name: &str, fallback: bool) -> bool {
285 self.get(user, param_name, fallback)
286 }
287
288 pub fn get_f64(&self, user: &StatsigUser, param_name: &str, fallback: f64) -> f64 {
289 self.get(user, param_name, fallback)
290 }
291
292 pub fn get_i64(&self, user: &StatsigUser, param_name: &str, fallback: i64) -> i64 {
293 self.get(user, param_name, fallback)
294 }
295
296 pub fn get_string(&self, user: &StatsigUser, param_name: &str, fallback: String) -> String {
297 self.get(user, param_name, fallback)
298 }
299
300 pub fn get_array(
301 &self,
302 user: &StatsigUser,
303 param_name: &str,
304 fallback: Vec<Value>,
305 ) -> Vec<Value> {
306 self.get(user, param_name, fallback)
307 }
308
309 pub fn get_object(
310 &self,
311 user: &StatsigUser,
312 param_name: &str,
313 fallback: HashMap<String, Value>,
314 ) -> HashMap<String, Value> {
315 self.get(user, param_name, fallback)
316 }
317}
318
319impl_common_get_methods!(DynamicConfig);
320impl_common_get_methods!(Experiment);
321impl_common_get_methods!(Layer);
322
323pub enum OverrideAdapterType {
324 LocalOverride,
325}
326
327fn extract_matching_type(
328 value: &HashMap<String, Value>,
329 param_name: &str,
330 fallback: &Option<Value>,
331) -> Option<Value> {
332 let found = value.get(param_name)?;
333 match (fallback, found) {
334 (Some(Value::Bool(_)), Value::Bool(_)) => Some(found.clone()),
335 (Some(Value::Number(_)), Value::Number(_)) => Some(found.clone()),
336 (Some(Value::String(_)), Value::String(_)) => Some(found.clone()),
337 (Some(Value::Array(_)), Value::Array(_)) => Some(found.clone()),
338 (Some(Value::Object(_)), Value::Object(_)) => Some(found.clone()),
339 (Some(Value::Null), Value::Null) => Some(found.clone()),
340 (None, value) => Some(value.clone()),
341 _ => None,
342 }
343}