1use serde::ser::SerializeStruct;
2use serde::{Serialize, Serializer};
3
4use crate::console_capture::console_capture_options::ConsoleCaptureOptions;
5use crate::data_store_interface::{DataStoreKeyVersion, DataStoreTrait};
6use crate::evaluation::dynamic_value::DynamicValue;
7use crate::event_logging::event_logger;
8use crate::event_logging_adapter::EventLoggingAdapter;
9use crate::id_lists_adapter::IdListsAdapter;
10use crate::networking::proxy_config::ProxyConfig;
11use crate::output_logger::{LogLevel, OutputLogProvider};
12use crate::persistent_storage::persistent_storage_trait::PersistentStorage;
13use crate::{
14 log_d, log_w, serialize_if_not_none, ConfigCompressionMode, ObservabilityClient,
15 OverrideAdapter, SpecAdapterConfig, SpecsAdapter,
16};
17use std::collections::{HashMap, HashSet};
18use std::fmt;
19use std::sync::{Arc, Weak};
20
21pub const DEFAULT_INIT_TIMEOUT_MS: u64 = 3000;
22const MIN_SYNC_INTERVAL: u32 = 1000;
23const TEST_ENV_FLAG: &str = "STATSIG_RUNNING_TESTS";
24
25#[derive(Clone, Default)]
26pub struct StatsigOptions {
27 pub data_store: Option<Arc<dyn DataStoreTrait>>, pub data_store_key_schema_version: Option<DataStoreKeyVersion>,
29
30 pub disable_all_logging: Option<bool>,
31 pub disable_country_lookup: Option<bool>,
32 pub disable_network: Option<bool>, pub enable_id_lists: Option<bool>,
35 pub enable_dcs_deltas: Option<bool>,
36 pub environment: Option<String>,
37 pub config_compression_mode: Option<ConfigCompressionMode>,
38
39 pub event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
40
41 #[deprecated]
42 pub event_logging_flush_interval_ms: Option<u32>,
43 pub event_logging_max_pending_batch_queue_size: Option<u32>,
44 pub event_logging_max_queue_size: Option<u32>,
45
46 pub fallback_to_statsig_api: Option<bool>,
47 pub global_custom_fields: Option<HashMap<String, DynamicValue>>,
48
49 pub id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
50 pub id_lists_sync_interval_ms: Option<u32>,
51 pub id_lists_url: Option<String>,
52
53 pub init_timeout_ms: Option<u64>,
54 pub log_event_url: Option<String>,
55 pub observability_client: Option<Weak<dyn ObservabilityClient>>,
56 pub output_log_level: Option<LogLevel>,
57 pub output_logger_provider: Option<Arc<dyn OutputLogProvider>>,
58 pub override_adapter: Option<Arc<dyn OverrideAdapter>>,
59 pub persistent_storage: Option<Arc<dyn PersistentStorage>>,
60 pub service_name: Option<String>,
61
62 pub spec_adapters_config: Option<Vec<SpecAdapterConfig>>, pub specs_adapter: Option<Arc<dyn SpecsAdapter>>,
64 pub specs_sync_interval_ms: Option<u32>,
65 pub specs_url: Option<String>,
66
67 pub wait_for_country_lookup_init: Option<bool>,
68 pub wait_for_user_agent_init: Option<bool>,
69
70 pub proxy_config: Option<ProxyConfig>,
71
72 pub console_capture_options: Option<ConsoleCaptureOptions>,
73
74 pub use_third_party_ua_parser: Option<bool>,
75 pub disable_disk_access: Option<bool>,
76
77 pub experimental_flags: Option<HashSet<String>>,
78}
79
80impl StatsigOptions {
81 #[must_use]
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 #[must_use]
88 pub fn builder() -> StatsigOptionsBuilder {
89 StatsigOptionsBuilder::default()
90 }
91}
92
93#[derive(Default)]
94pub struct StatsigOptionsBuilder {
95 inner: StatsigOptions,
96}
97
98impl StatsigOptionsBuilder {
99 #[must_use]
100 pub fn new() -> Self {
101 Self::default()
102 }
103
104 #[must_use]
107 pub fn specs_url(mut self, specs_url: Option<String>) -> Self {
108 self.inner.specs_url = specs_url;
109 self
110 }
111
112 #[must_use]
113 pub fn specs_adapter(mut self, specs_adapter: Option<Arc<dyn SpecsAdapter>>) -> Self {
114 self.inner.specs_adapter = specs_adapter;
115 self
116 }
117
118 #[must_use]
119 pub fn specs_sync_interval_ms(mut self, specs_sync_interval_ms: Option<u32>) -> Self {
120 self.inner.specs_sync_interval_ms = specs_sync_interval_ms;
121 self
122 }
123
124 #[must_use]
125 pub fn spec_adapters_config(
126 mut self,
127 spec_adapters_config: Option<Vec<SpecAdapterConfig>>,
128 ) -> Self {
129 self.inner.spec_adapters_config = spec_adapters_config;
130 self
131 }
132
133 #[must_use]
136 pub fn log_event_url(mut self, log_event_url: Option<String>) -> Self {
137 self.inner.log_event_url = log_event_url;
138 self
139 }
140
141 #[must_use]
142 pub fn disable_all_logging(mut self, disable_all_logging: Option<bool>) -> Self {
143 self.inner.disable_all_logging = disable_all_logging;
144 self
145 }
146
147 #[must_use]
148 pub fn event_logging_adapter(
149 mut self,
150 event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
151 ) -> Self {
152 self.inner.event_logging_adapter = event_logging_adapter;
153 self
154 }
155
156 #[must_use]
157 #[deprecated(
158 note = "This field is deprecated in favor of smart log event. It is no longer consumed and can be removed safely."
159 )]
160 #[allow(deprecated)]
161 pub fn event_logging_flush_interval_ms(
162 mut self,
163 event_logging_flush_interval_ms: Option<u32>,
164 ) -> Self {
165 self.inner.event_logging_flush_interval_ms = event_logging_flush_interval_ms;
166 self
167 }
168
169 #[must_use]
170 pub fn event_logging_max_queue_size(
171 mut self,
172 event_logging_max_queue_size: Option<u32>,
173 ) -> Self {
174 self.inner.event_logging_max_queue_size = event_logging_max_queue_size;
175 self
176 }
177
178 #[must_use]
179 pub fn event_logging_max_pending_batch_queue_size(
180 mut self,
181 event_logging_max_pending_batch_queue_size: Option<u32>,
182 ) -> Self {
183 self.inner.event_logging_max_pending_batch_queue_size =
184 event_logging_max_pending_batch_queue_size;
185 self
186 }
187
188 #[must_use]
191 pub fn enable_id_lists(mut self, enable_id_lists: Option<bool>) -> Self {
192 self.inner.enable_id_lists = enable_id_lists;
193 self
194 }
195
196 #[must_use]
197 pub fn enable_dcs_deltas(mut self, enable_dcs_deltas: Option<bool>) -> Self {
198 self.inner.enable_dcs_deltas = enable_dcs_deltas;
199 self
200 }
201
202 #[must_use]
203 pub fn id_lists_url(mut self, id_lists_url: Option<String>) -> Self {
204 self.inner.id_lists_url = id_lists_url;
205 self
206 }
207
208 #[must_use]
209 pub fn id_lists_adapter(mut self, id_lists_adapter: Option<Arc<dyn IdListsAdapter>>) -> Self {
210 self.inner.id_lists_adapter = id_lists_adapter;
211 self
212 }
213
214 #[must_use]
215 pub fn id_lists_sync_interval_ms(mut self, id_lists_sync_interval_ms: Option<u32>) -> Self {
216 self.inner.id_lists_sync_interval_ms = id_lists_sync_interval_ms;
217 self
218 }
219
220 #[must_use]
223 pub fn proxy_config(mut self, proxy_config: Option<ProxyConfig>) -> Self {
224 self.inner.proxy_config = proxy_config;
225 self
226 }
227
228 #[must_use]
229 pub fn environment(mut self, environment: Option<String>) -> Self {
230 self.inner.environment = environment;
231 self
232 }
233
234 #[must_use]
235 #[deprecated(
236 note = "This field is deprecated and will be removed in a future release. It is no longer consumed and can be removed safely."
237 )]
238 pub fn config_compression_mode(
239 mut self,
240 config_compression_mode: Option<ConfigCompressionMode>,
241 ) -> Self {
242 self.inner.config_compression_mode = config_compression_mode;
243 self
244 }
245
246 #[must_use]
247 pub fn output_log_level(mut self, output_log_level: Option<u32>) -> Self {
248 if let Some(level) = output_log_level {
249 self.inner.output_log_level = Some(LogLevel::from(level));
250 }
251 self
252 }
253
254 #[must_use]
255 pub fn output_logger_provider(
256 mut self,
257 output_logger_provider: Option<Arc<dyn OutputLogProvider>>,
258 ) -> Self {
259 self.inner.output_logger_provider = output_logger_provider;
260 self
261 }
262
263 #[must_use]
264 pub fn wait_for_country_lookup_init(
265 mut self,
266 wait_for_country_lookup_init: Option<bool>,
267 ) -> Self {
268 self.inner.wait_for_country_lookup_init = wait_for_country_lookup_init;
269 self
270 }
271
272 #[must_use]
273 pub fn wait_for_user_agent_init(mut self, wait_for_user_agent_init: Option<bool>) -> Self {
274 self.inner.wait_for_user_agent_init = wait_for_user_agent_init;
275 self
276 }
277
278 #[must_use]
279 pub fn disable_country_lookup(mut self, disable_country_lookup: Option<bool>) -> Self {
280 self.inner.disable_country_lookup = disable_country_lookup;
281 self
282 }
283
284 #[must_use]
285 pub fn service_name(mut self, service_name: Option<String>) -> Self {
286 self.inner.service_name = service_name;
287 self
288 }
289
290 #[must_use]
291 pub fn fallback_to_statsig_api(mut self, fallback_to_statsig_api: Option<bool>) -> Self {
292 self.inner.fallback_to_statsig_api = fallback_to_statsig_api;
293 self
294 }
295
296 #[must_use]
297 pub fn global_custom_fields(
298 mut self,
299 global_custom_fields: Option<HashMap<String, DynamicValue>>,
300 ) -> Self {
301 self.inner.global_custom_fields = global_custom_fields;
302 self
303 }
304
305 pub fn disable_network(mut self, disable_network: Option<bool>) -> Self {
306 self.inner.disable_network = disable_network;
307 self
308 }
309
310 #[must_use]
311 pub fn use_third_party_ua_parser(mut self, use_third_party_ua_parser: Option<bool>) -> Self {
312 self.inner.use_third_party_ua_parser = use_third_party_ua_parser;
313 self
314 }
315
316 #[must_use]
317 pub fn init_timeout_ms(mut self, init_timeout_ms: Option<u64>) -> Self {
318 self.inner.init_timeout_ms = init_timeout_ms;
319 self
320 }
321
322 #[must_use]
323 pub fn build(self) -> StatsigOptions {
324 self.inner
325 }
326
327 #[must_use]
330 pub fn persistent_storage(
331 mut self,
332 persistent_storage: Option<Arc<dyn PersistentStorage>>,
333 ) -> Self {
334 self.inner.persistent_storage = persistent_storage;
335 self
336 }
337
338 #[must_use]
339 pub fn observability_client(mut self, client: Option<Weak<dyn ObservabilityClient>>) -> Self {
340 self.inner.observability_client = client;
341 self
342 }
343
344 #[must_use]
345 pub fn data_store(mut self, data_store: Option<Arc<dyn DataStoreTrait>>) -> Self {
346 self.inner.data_store = data_store;
347 self
348 }
349}
350
351impl Serialize for StatsigOptions {
352 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
353 where
354 S: Serializer,
355 {
356 let mut state = serializer.serialize_struct("StatsigOptions", 21)?;
357 serialize_if_not_none!(state, "spec_url", &self.specs_url);
358 serialize_if_not_none!(
359 state,
360 "spec_adapter",
361 &get_display_name(&self.specs_adapter)
362 );
363 serialize_if_not_none!(state, "spec_adapter_configs", &self.spec_adapters_config);
364 serialize_if_not_none!(
365 state,
366 "specs_sync_interval_ms",
367 &self.specs_sync_interval_ms
368 );
369 serialize_if_not_none!(state, "init_timeout_ms", &self.init_timeout_ms);
370
371 serialize_if_not_none!(state, "data_store", &get_if_set(&self.data_store));
372
373 serialize_if_not_none!(state, "log_event_url", &self.log_event_url);
374 serialize_if_not_none!(state, "disable_all_logging", &self.disable_all_logging);
375 serialize_if_not_none!(state, "disable_network", &self.disable_network);
376
377 serialize_if_not_none!(state, "id_lists_url", &self.id_lists_url);
378 serialize_if_not_none!(state, "enable_id_lists", &self.enable_id_lists);
379 serialize_if_not_none!(state, "enable_dcs_deltas", &self.enable_dcs_deltas);
380 serialize_if_not_none!(
381 state,
382 "wait_for_user_agent_init",
383 &self.wait_for_user_agent_init
384 );
385 serialize_if_not_none!(
386 state,
387 "wait_for_country_lookup_init",
388 &self.wait_for_country_lookup_init
389 );
390 serialize_if_not_none!(
391 state,
392 "id_lists_sync_interval",
393 &self.id_lists_sync_interval_ms
394 );
395 serialize_if_not_none!(state, "environment", &self.environment);
396 serialize_if_not_none!(
397 state,
398 "id_list_adapter",
399 &get_display_name(&self.id_lists_adapter)
400 );
401 serialize_if_not_none!(
402 state,
403 "fallback_to_statsig_api",
404 &self.fallback_to_statsig_api
405 );
406 serialize_if_not_none!(
407 state,
408 "override_adapter",
409 &get_if_set(&self.override_adapter)
410 );
411 serialize_if_not_none!(state, "service_name", &get_if_set(&self.service_name));
412 serialize_if_not_none!(state, "global_custom_fields", &self.global_custom_fields);
413
414 state.end()
415 }
416}
417
418fn get_if_set<T>(s: &Option<T>) -> Option<&str> {
419 s.as_ref().map(|_| "set")
420}
421
422fn get_display_name<T: fmt::Debug>(s: &Option<T>) -> Option<String> {
423 s.as_ref().map(|st| format!("{st:?}"))
424}
425
426const TAG: &str = "StatsigOptionValidator";
429impl StatsigOptions {
430 pub fn validate_and_fix(self: Arc<Self>) -> Arc<Self> {
431 if std::env::var(TEST_ENV_FLAG).is_ok() {
432 log_d!(
433 TAG,
434 "Skipping StatsigOptions validation in testing environment"
435 );
436 return self;
437 }
438
439 let mut opts_clone: Arc<StatsigOptions> = self.clone();
440 let mut_ref = Arc::make_mut(&mut opts_clone);
441
442 if is_sync_interval_invalid(&self.specs_sync_interval_ms) {
443 log_w!(
444 TAG,
445 "Invalid 'specs_sync_interval_ms', value must be greater than {}, received {:?}",
446 MIN_SYNC_INTERVAL,
447 &self.specs_sync_interval_ms
448 );
449 mut_ref.specs_sync_interval_ms = None;
450 }
451
452 if is_sync_interval_invalid(&self.id_lists_sync_interval_ms) {
453 log_w!(
454 TAG,
455 "Invalid 'id_lists_sync_interval_ms', value must be greater than {}, received {:?}",
456 MIN_SYNC_INTERVAL,
457 &self.id_lists_sync_interval_ms
458 );
459 mut_ref.id_lists_sync_interval_ms = None;
460 }
461
462 if bounds_check_logging_batch_size(&self.event_logging_max_queue_size) {
463 log_w!(
464 TAG,
465 "Invalid 'event_logging_max_queue_size', value cannot be lower than {} or greater than {}, received {:?}",
466 event_logger::MIN_BATCH_SIZE,
467 event_logger::MAX_BATCH_SIZE,
468 &self.event_logging_max_queue_size
469 );
470 mut_ref.event_logging_max_queue_size = None;
471 }
472
473 if bounds_check_loggging_pending_queue_size(
474 &self.event_logging_max_pending_batch_queue_size,
475 ) {
476 log_w!(
477 TAG,
478 "Invalid 'event_logging_max_pending_batch_queue_size', value cannot be lower than {}, received {:?}",
479 event_logger::MIN_PENDING_BATCH_COUNT,
480 &self.event_logging_max_pending_batch_queue_size
481 );
482 mut_ref.event_logging_max_pending_batch_queue_size = None;
483 }
484
485 if should_fix_null_url(&self.specs_url) {
486 log_d!(TAG, "Setting specs_url to be default url");
487 mut_ref.specs_url = None;
488 }
489
490 if should_fix_null_url(&self.id_lists_url) {
491 log_d!(TAG, "Setting id_lists_url to be default url");
492 mut_ref.id_lists_url = None;
493 }
494
495 if should_fix_null_url(&self.log_event_url) {
496 log_d!(TAG, "Setting log_event_url to be default url");
497 mut_ref.log_event_url = None;
498 }
499
500 opts_clone
501 }
502}
503
504fn is_sync_interval_invalid(interval_ms: &Option<u32>) -> bool {
505 if let Some(interval) = interval_ms {
506 return *interval < MIN_SYNC_INTERVAL;
507 }
508 false
509}
510
511fn should_fix_null_url(maybe_url: &Option<String>) -> bool {
512 if let Some(url) = maybe_url {
513 return url.is_empty() || url.eq_ignore_ascii_case("null");
514 }
515
516 false
517}
518
519fn bounds_check_logging_batch_size(batch_size: &Option<u32>) -> bool {
520 if let Some(batch_size) = batch_size {
521 return *batch_size < event_logger::MIN_BATCH_SIZE
522 || *batch_size > event_logger::MAX_BATCH_SIZE;
523 }
524 false
525}
526
527fn bounds_check_loggging_pending_queue_size(queue_size: &Option<u32>) -> bool {
528 if let Some(queue_size) = queue_size {
529 return *queue_size < event_logger::MIN_PENDING_BATCH_COUNT;
530 }
531 false
532}