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