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