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