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