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.parameters.get(param_name)?;
220 match param {
221 Parameter::StaticValue(static_value) => from_value(static_value.value.clone()).ok(),
222 Parameter::Gate(gate) => {
223 let res = self._statsig_ref.check_gate_with_options(
224 user,
225 &gate.gate_name,
226 self.options.into(),
227 );
228 let val = match res {
229 true => gate.pass_value.clone(),
230 false => gate.fail_value.clone(),
231 };
232 from_value(val).ok()
233 }
234 Parameter::DynamicConfig(dynamic_config) => {
235 let res = self._statsig_ref.get_dynamic_config_with_options(
236 user,
237 &dynamic_config.config_name,
238 self.options.into(),
239 );
240 res.get_opt(&dynamic_config.param_name)?
241 }
242 Parameter::Experiment(experiment) => {
243 let res = self._statsig_ref.get_experiment_with_options(
244 user,
245 &experiment.experiment_name,
246 self.options.into(),
247 );
248 res.get_opt(&experiment.param_name)?
249 }
250 Parameter::Layer(layer) => {
251 let res = self._statsig_ref.get_layer_with_options(
252 user,
253 &layer.layer_name,
254 self.options.into(),
255 );
256 res.get_opt(&layer.param_name)?
257 }
258 }
259 }
260
261 pub fn get<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str, fallback: T) -> T {
262 self.get_opt(user, param_name).unwrap_or(fallback)
263 }
264
265 pub fn get_json_value(
266 &self,
267 user: &StatsigUser,
268 param_name: &str,
269 fallback: Option<Value>,
270 ) -> Value {
271 match fallback {
272 None | Some(Value::Null) => self
273 .get_opt::<Value>(user, param_name)
274 .unwrap_or(Value::Null),
275 Some(Value::Bool(boolean)) => self.get_bool(user, param_name, boolean).into(),
276 Some(Value::Number(number)) => self.get(user, param_name, number).into(),
277 Some(Value::String(string)) => self.get_string(user, param_name, string).into(),
278 Some(Value::Array(vec)) => self.get_array(user, param_name, vec).into(),
279 Some(Value::Object(map)) => self
280 .get_object(user, param_name, map.into_iter().collect())
281 .into_iter()
282 .collect(),
283 }
284 }
285
286 pub fn get_bool(&self, user: &StatsigUser, param_name: &str, fallback: bool) -> bool {
287 self.get(user, param_name, fallback)
288 }
289
290 pub fn get_f64(&self, user: &StatsigUser, param_name: &str, fallback: f64) -> f64 {
291 self.get(user, param_name, fallback)
292 }
293
294 pub fn get_i64(&self, user: &StatsigUser, param_name: &str, fallback: i64) -> i64 {
295 self.get(user, param_name, fallback)
296 }
297
298 pub fn get_string(&self, user: &StatsigUser, param_name: &str, fallback: String) -> String {
299 self.get(user, param_name, fallback)
300 }
301
302 pub fn get_array(
303 &self,
304 user: &StatsigUser,
305 param_name: &str,
306 fallback: Vec<Value>,
307 ) -> Vec<Value> {
308 self.get(user, param_name, fallback)
309 }
310
311 pub fn get_object(
312 &self,
313 user: &StatsigUser,
314 param_name: &str,
315 fallback: HashMap<String, Value>,
316 ) -> HashMap<String, Value> {
317 self.get(user, param_name, fallback)
318 }
319}
320
321impl_common_get_methods!(DynamicConfig);
322impl_common_get_methods!(Experiment);
323impl_common_get_methods!(Layer);
324
325pub enum OverrideAdapterType {
326 LocalOverride,
327}
328
329fn extract_matching_type(
330 value: &HashMap<String, Value>,
331 param_name: &str,
332 fallback: &Option<Value>,
333) -> Option<Value> {
334 let found = value.get(param_name)?;
335 match (fallback, found) {
336 (Some(Value::Bool(_)), Value::Bool(_)) => Some(found.clone()),
337 (Some(Value::Number(_)), Value::Number(_)) => Some(found.clone()),
338 (Some(Value::String(_)), Value::String(_)) => Some(found.clone()),
339 (Some(Value::Array(_)), Value::Array(_)) => Some(found.clone()),
340 (Some(Value::Object(_)), Value::Object(_)) => Some(found.clone()),
341 (Some(Value::Null), Value::Null) => Some(found.clone()),
342 (None, value) => Some(value.clone()),
343 _ => None,
344 }
345}