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