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