statsig_rust/specs_adapter/
statsig_bootstrap_specs_adapter.rs1use crate::specs_adapter::{SpecsAdapter, SpecsSource, SpecsUpdate, SpecsUpdateListener};
2use crate::statsig_err::StatsigErr;
3use crate::{log_e, StatsigRuntime};
4use async_trait::async_trait;
5use chrono::Utc;
6
7use std::sync::{Arc, RwLock};
8use std::time::Duration;
9pub struct StatsigBootstrapSpecsAdapter {
10 data: RwLock<String>,
11 listener: RwLock<Option<Arc<dyn SpecsUpdateListener>>>,
12}
13const TAG: &str = stringify!(StatsigBootstrapSpecsAdapter);
14
15impl StatsigBootstrapSpecsAdapter {
16 #[must_use]
17 pub fn new(data: String) -> Self {
18 Self {
19 data: RwLock::new(data),
20 listener: RwLock::new(None),
21 }
22 }
23
24 pub fn set_data(&self, data: String) -> Result<(), StatsigErr> {
25 match self.data.write() {
26 Ok(mut lock) => *lock = data.clone(),
27 Err(e) => return Err(StatsigErr::LockFailure(e.to_string())),
28 };
29
30 self.push_update()
31 }
32
33 fn push_update(&self) -> Result<(), StatsigErr> {
34 let data = match self.data.read() {
35 Ok(lock) => lock.clone(),
36 Err(e) => return Err(StatsigErr::LockFailure(e.to_string())),
37 };
38
39 match &self.listener.read() {
40 Ok(lock) => match lock.as_ref() {
41 Some(listener) => listener.did_receive_specs_update(SpecsUpdate {
42 data: data.into_bytes(),
43 source: SpecsSource::Bootstrap,
44 received_at: Utc::now().timestamp_millis() as u64,
45 source_api: None,
46 }),
47 None => Err(StatsigErr::UnstartedAdapter("Listener not set".to_string())),
48 },
49 Err(e) => Err(StatsigErr::LockFailure(e.to_string())),
50 }
51 }
52}
53
54#[async_trait]
55impl SpecsAdapter for StatsigBootstrapSpecsAdapter {
56 async fn start(
57 self: Arc<Self>,
58 _statsig_runtime: &Arc<StatsigRuntime>,
59 ) -> Result<(), StatsigErr> {
60 self.push_update()
61 }
62
63 fn initialize(&self, listener: Arc<dyn SpecsUpdateListener>) {
64 match self.listener.write() {
65 Ok(mut lock) => *lock = Some(listener),
66 Err(e) => {
67 log_e!(TAG, "Failed to acquire write lock on listener: {}", e);
68 }
69 }
70 }
71
72 async fn shutdown(
73 &self,
74 _timeout: Duration,
75 _statsig_runtime: &Arc<StatsigRuntime>,
76 ) -> Result<(), StatsigErr> {
77 Ok(())
78 }
79
80 async fn schedule_background_sync(
81 self: Arc<Self>,
82 _statsig_runtime: &Arc<StatsigRuntime>,
83 ) -> Result<(), StatsigErr> {
84 Ok(())
85 }
86
87 fn get_type_name(&self) -> String {
88 stringify!(StatsigBootstrapSpecsAdapter).to_string()
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use crate::SpecsInfo;
95
96 use super::*;
97 use std::sync::Arc;
98
99 struct TestListener {
100 received_update: RwLock<Option<SpecsUpdate>>,
101 }
102
103 impl TestListener {
104 fn new() -> Self {
105 Self {
106 received_update: RwLock::new(None),
107 }
108 }
109 }
110
111 #[async_trait]
112 impl SpecsUpdateListener for TestListener {
113 fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr> {
114 if let Ok(mut lock) = self.received_update.write() {
115 *lock = Some(update);
116 }
117 Ok(())
118 }
119
120 fn get_current_specs_info(&self) -> SpecsInfo {
121 SpecsInfo::empty()
122 }
123 }
124
125 #[tokio::test]
126 async fn test_manually_sync_specs() {
127 let test_data = serde_json::json!({
128 "feature_gates": {},
129 "dynamic_configs": {},
130 "layer_configs": {},
131 })
132 .to_string();
133
134 let adapter = Arc::new(StatsigBootstrapSpecsAdapter::new(test_data.clone()));
135 let listener = Arc::new(TestListener::new());
136
137 let statsig_rt = StatsigRuntime::get_runtime();
138 adapter.initialize(listener.clone());
139 adapter.clone().start(&statsig_rt).await.unwrap();
140
141 if let Ok(lock) = listener.clone().received_update.read() {
142 let update = lock.as_ref().unwrap();
143 assert_eq!(update.source, SpecsSource::Bootstrap);
144 assert_eq!(update.data, test_data.into_bytes());
145 }
146 }
147
148 #[tokio::test]
149 async fn test_set_data() {
150 let statsig_rt = StatsigRuntime::get_runtime();
151
152 let adapter = Arc::new(StatsigBootstrapSpecsAdapter::new(String::new()));
153
154 let listener = Arc::new(TestListener::new());
155 adapter.initialize(listener.clone());
156 adapter.clone().start(&statsig_rt).await.unwrap();
157
158 let test_data = "{\"some\": \"value\"}".to_string();
159 let result = adapter.set_data(test_data.clone());
160 assert!(result.is_ok());
161
162 if let Ok(lock) = listener.clone().received_update.read() {
163 let update = lock.as_ref().unwrap();
164 assert_eq!(update.source, SpecsSource::Bootstrap);
165 assert_eq!(update.data, test_data.into_bytes());
166 }
167 }
168}