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