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