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(Default, Clone)]
23#[cfg(feature = "ffi-support")]
24pub struct BulkEvaluationOptions {
25 pub feature_gate_filter: Option<Vec<String>>,
26 pub dynamic_config_filter: Option<Vec<String>>,
27 pub experiment_filter: Option<Vec<String>>,
28 pub layer_filter: Option<Vec<String>>,
29 pub include_local_override: bool,
30}
31
32#[derive(Default, Clone)]
33#[cfg(feature = "ffi-support")]
34pub struct ResolvedBulkEvaluationOptions {
35 pub feature_gates: Vec<String>,
36 pub dynamic_configs: Vec<String>,
37 pub experiments: Vec<String>,
38 pub layers: Vec<String>,
39 pub include_local_override: bool,
40}
41
42#[derive(Serialize, Deserialize, Clone, Default)]
43#[cfg(feature = "ffi-support")]
44pub struct BulkEvaluationResponse {
45 pub feature_gates: HashMap<String, BulkFeatureGateEvaluation>,
46 pub dynamic_configs: HashMap<String, BulkDynamicConfigEvaluation>,
47 pub experiments: HashMap<String, BulkExperimentEvaluation>,
48 pub layer_configs: HashMap<String, BulkLayerEvaluation>,
49}
50
51#[derive(Serialize, Deserialize, Clone)]
52#[cfg(feature = "ffi-support")]
53pub struct BulkEvaluationCommonFields {
54 #[serde(rename = "ruleID")]
55 pub rule_id: InternedString,
56 #[serde(rename = "idType")]
57 pub id_type: Option<InternedString>,
58 pub details: EvaluationDetails,
59 #[serde(rename = "exposureToken")]
60 pub exposure_token: Option<String>,
61}
62
63#[derive(Serialize, Deserialize, Clone)]
64#[cfg(feature = "ffi-support")]
65pub struct BulkFeatureGateEvaluation {
66 pub value: bool,
67 #[serde(flatten)]
68 pub common: BulkEvaluationCommonFields,
69}
70
71#[derive(Serialize, Deserialize, Clone)]
72#[cfg(feature = "ffi-support")]
73pub struct BulkDynamicConfigEvaluation {
74 pub value: HashMap<String, Value>,
75 #[serde(flatten)]
76 pub common: BulkEvaluationCommonFields,
77}
78
79#[derive(Serialize, Deserialize, Clone)]
80#[cfg(feature = "ffi-support")]
81pub struct BulkExperimentEvaluation {
82 pub value: HashMap<String, Value>,
83 #[serde(rename = "groupName")]
84 pub group_name: Option<String>,
85 #[serde(flatten)]
86 pub common: BulkEvaluationCommonFields,
87}
88
89#[derive(Serialize, Deserialize, Clone)]
90#[cfg(feature = "ffi-support")]
91pub struct BulkLayerEvaluation {
92 pub value: HashMap<String, Value>,
93 #[serde(rename = "groupName")]
94 pub group_name: Option<String>,
95 #[serde(rename = "allocatedExperimentName")]
96 pub allocated_experiment_name: Option<String>,
97 #[serde(flatten)]
98 pub common: BulkEvaluationCommonFields,
99}
100
101#[derive(Serialize, Deserialize, Clone)]
102pub struct FeatureGate {
103 pub name: String,
104 pub value: bool,
105 pub rule_id: String,
106 pub id_type: String,
107 pub details: EvaluationDetails,
108
109 pub(crate) __evaluation: Option<GateEvaluation>,
110}
111
112#[derive(Serialize, Deserialize, Clone)]
113pub struct DynamicConfig {
114 pub name: String,
115 pub value: HashMap<String, Value>,
116 pub rule_id: String,
117 pub id_type: String,
118 pub details: EvaluationDetails,
119
120 pub __evaluation: Option<DynamicConfigEvaluation>,
121}
122
123impl DynamicConfig {
124 #[must_use]
125 pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
126 match self.value.get(param_name) {
127 Some(value) => from_value(value.clone()).ok(),
128 None => None,
129 }
130 }
131
132 pub fn get_typed_opt(&self, param_name: &str, fallback: Option<Value>) -> Option<Value> {
133 extract_matching_type(&self.value, param_name, &fallback).or(fallback)
134 }
135}
136
137#[derive(Serialize, Deserialize, Clone)]
138pub struct Experiment {
139 pub name: String,
140 pub value: HashMap<String, Value>,
141 pub rule_id: String,
142 pub id_type: String,
143 pub group_name: Option<String>,
144 pub details: EvaluationDetails,
145 pub is_experiment_active: bool,
146
147 pub __evaluation: Option<ExperimentEvaluation>,
148}
149
150impl Experiment {
151 #[must_use]
152 pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
153 match self.value.get(param_name) {
154 Some(value) => from_value(value.clone()).ok(),
155 None => None,
156 }
157 }
158
159 pub fn get_typed_opt(&self, param_name: &str, fallback: Option<Value>) -> Option<Value> {
160 extract_matching_type(&self.value, param_name, &fallback).or(fallback)
161 }
162}
163
164#[derive(Serialize, Deserialize, Clone)]
165pub struct Layer {
166 pub name: String,
167 pub rule_id: String,
168 pub id_type: String,
169
170 pub group_name: Option<String>,
171 pub details: EvaluationDetails,
172 pub allocated_experiment_name: Option<String>,
173 pub is_experiment_active: bool,
174
175 pub __parameter_rule_ids: Option<HashMap<InternedString, InternedString>>,
176 pub __evaluation: Option<LayerEvaluation>,
177 pub __value: HashMap<String, Value>,
178 pub __user: StatsigUserLoggable,
179 pub __disable_exposure: bool,
180
181 pub __version: Option<u32>, #[serde(skip_serializing_if = "Option::is_none")]
184 pub __exposure_info: Option<ExtraExposureInfo>,
185
186 #[serde(skip_serializing, skip_deserializing)]
187 pub __event_logger_ptr: Option<Weak<EventLogger>>,
188}
189
190impl Layer {
191 pub fn get_opt<T: DeserializeOwned>(&self, param_name: &str) -> Option<T> {
192 let value = match self.__value.get(param_name) {
193 Some(value) => value.clone(),
194 None => return None,
195 };
196
197 match from_value(value.clone()) {
198 Ok(value) => {
199 self.log_param_exposure(param_name);
200 Some(value)
201 }
202 Err(_) => None,
203 }
204 }
205
206 pub fn get_typed_opt(&self, param_name: &str, fallback: Option<Value>) -> Option<Value> {
207 match extract_matching_type(&self.__value, param_name, &fallback) {
208 Some(value) => {
209 self.log_param_exposure(param_name);
210 Some(value)
211 }
212 None => fallback,
213 }
214 }
215
216 pub fn get_raw_value(&self, param_name: &str) -> Option<Value> {
217 match self.__value.get(param_name) {
218 Some(value) => {
219 self.log_param_exposure(param_name);
220 Some(value.clone())
221 }
222 None => None,
223 }
224 }
225
226 fn log_param_exposure(&self, param_name: &str) -> Option<()> {
227 let logger = self.__event_logger_ptr.as_ref()?.upgrade()?;
228
229 if self.__disable_exposure {
230 logger.increment_non_exposure_checks(&self.name);
231 return None;
232 }
233
234 logger.enqueue(EnqueueLayerParamExpoOp::LayerRef(
235 Utc::now().timestamp_millis() as u64,
236 self,
237 param_name,
238 ExposureTrigger::Auto,
239 ));
240
241 None
242 }
243}
244
245macro_rules! impl_common_get_methods {
246 ($struct_name:ident) => {
247 impl $struct_name {
248 pub fn get<T: DeserializeOwned>(&self, param_name: &str, fallback: T) -> T {
249 self.get_opt(param_name).unwrap_or_else(|| fallback)
250 }
251
252 #[must_use]
253 pub fn get_bool(&self, param_name: &str, fallback: bool) -> bool {
254 self.get(param_name, fallback)
255 }
256
257 #[must_use]
258 pub fn get_f64(&self, param_name: &str, fallback: f64) -> f64 {
259 self.get(param_name, fallback)
260 }
261
262 #[must_use]
263 pub fn get_i64(&self, param_name: &str, fallback: i64) -> i64 {
264 self.get(param_name, fallback)
265 }
266
267 #[must_use]
268 pub fn get_string(&self, param_name: &str, fallback: String) -> String {
269 self.get(param_name, fallback)
270 }
271
272 #[must_use]
273 pub fn get_array(&self, param_name: &str, fallback: Vec<Value>) -> Vec<Value> {
274 self.get(param_name, fallback)
275 }
276
277 #[must_use]
278 pub fn get_object(
279 &self,
280 param_name: &str,
281 fallback: HashMap<String, Value>,
282 ) -> HashMap<String, Value> {
283 self.get(param_name, fallback)
284 }
285 }
286 };
287}
288
289#[derive(Serialize, Clone)]
290pub struct ParameterStore<'a> {
291 pub name: String,
292 pub details: EvaluationDetails,
293 pub parameters: HashMap<String, Parameter>,
294 pub options: ParameterStoreEvaluationOptions,
295
296 #[serde(skip_serializing, skip_deserializing)]
297 pub _statsig_ref: &'a Statsig,
298}
299
300impl ParameterStore<'_> {
301 pub fn get_opt<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str) -> Option<T> {
302 let param = self.parameters.get(param_name)?;
303 match param {
304 Parameter::StaticValue(static_value) => from_value(static_value.value.clone()).ok(),
305 Parameter::Gate(gate) => {
306 let res = self._statsig_ref.check_gate_with_options(
307 user,
308 &gate.gate_name,
309 self.options.into(),
310 );
311 let val = match res {
312 true => gate.pass_value.clone(),
313 false => gate.fail_value.clone(),
314 };
315 from_value(val).ok()
316 }
317 Parameter::DynamicConfig(dynamic_config) => {
318 let res = self._statsig_ref.get_dynamic_config_with_options(
319 user,
320 &dynamic_config.config_name,
321 self.options.into(),
322 );
323 res.get_opt(&dynamic_config.param_name)?
324 }
325 Parameter::Experiment(experiment) => {
326 let res = self._statsig_ref.get_experiment_with_options(
327 user,
328 &experiment.experiment_name,
329 self.options.into(),
330 );
331 res.get_opt(&experiment.param_name)?
332 }
333 Parameter::Layer(layer) => {
334 let res = self._statsig_ref.get_layer_with_options(
335 user,
336 &layer.layer_name,
337 self.options.into(),
338 );
339 res.get_opt(&layer.param_name)?
340 }
341 }
342 }
343
344 pub fn get<T: DeserializeOwned>(&self, user: &StatsigUser, param_name: &str, fallback: T) -> T {
345 self.get_opt(user, param_name).unwrap_or(fallback)
346 }
347
348 pub fn get_json_value(
349 &self,
350 user: &StatsigUser,
351 param_name: &str,
352 fallback: Option<Value>,
353 ) -> Value {
354 match fallback {
355 None | Some(Value::Null) => self
356 .get_opt::<Value>(user, param_name)
357 .unwrap_or(Value::Null),
358 Some(Value::Bool(boolean)) => self.get_bool(user, param_name, boolean).into(),
359 Some(Value::Number(number)) => self.get(user, param_name, number).into(),
360 Some(Value::String(string)) => self.get_string(user, param_name, string).into(),
361 Some(Value::Array(vec)) => self.get_array(user, param_name, vec).into(),
362 Some(Value::Object(map)) => self
363 .get_object(user, param_name, map.into_iter().collect())
364 .into_iter()
365 .collect(),
366 }
367 }
368
369 pub fn get_bool(&self, user: &StatsigUser, param_name: &str, fallback: bool) -> bool {
370 self.get(user, param_name, fallback)
371 }
372
373 pub fn get_f64(&self, user: &StatsigUser, param_name: &str, fallback: f64) -> f64 {
374 self.get(user, param_name, fallback)
375 }
376
377 pub fn get_i64(&self, user: &StatsigUser, param_name: &str, fallback: i64) -> i64 {
378 self.get(user, param_name, fallback)
379 }
380
381 pub fn get_string(&self, user: &StatsigUser, param_name: &str, fallback: String) -> String {
382 self.get(user, param_name, fallback)
383 }
384
385 pub fn get_array(
386 &self,
387 user: &StatsigUser,
388 param_name: &str,
389 fallback: Vec<Value>,
390 ) -> Vec<Value> {
391 self.get(user, param_name, fallback)
392 }
393
394 pub fn get_object(
395 &self,
396 user: &StatsigUser,
397 param_name: &str,
398 fallback: HashMap<String, Value>,
399 ) -> HashMap<String, Value> {
400 self.get(user, param_name, fallback)
401 }
402}
403
404impl_common_get_methods!(DynamicConfig);
405impl_common_get_methods!(Experiment);
406impl_common_get_methods!(Layer);
407
408pub enum OverrideAdapterType {
409 LocalOverride,
410}
411
412fn extract_matching_type(
413 value: &HashMap<String, Value>,
414 param_name: &str,
415 fallback: &Option<Value>,
416) -> Option<Value> {
417 let found = value.get(param_name)?;
418 match (fallback, found) {
419 (Some(Value::Bool(_)), Value::Bool(_)) => Some(found.clone()),
420 (Some(Value::Number(_)), Value::Number(_)) => Some(found.clone()),
421 (Some(Value::String(_)), Value::String(_)) => Some(found.clone()),
422 (Some(Value::Array(_)), Value::Array(_)) => Some(found.clone()),
423 (Some(Value::Object(_)), Value::Object(_)) => Some(found.clone()),
424 (Some(Value::Null), Value::Null) => Some(found.clone()),
425 (None, value) => Some(value.clone()),
426 _ => None,
427 }
428}