apollo_rust_client/
lib.rs1use cache::Cache;
2use client_config::ClientConfig;
3use futures::executor::block_on;
4use log::trace;
5use std::{
6 collections::HashMap,
7 sync::{Arc, RwLock},
8 thread::{self, JoinHandle},
9 time::Duration,
10};
11
12pub mod cache;
13pub mod client_config;
14
15#[derive(Debug, thiserror::Error)]
17pub enum Error {
18 #[error("Client is already running")]
19 AlreadyRunning,
20}
21
22pub struct Client {
24 client_config: ClientConfig,
25 namespaces: Arc<RwLock<HashMap<String, Arc<Cache>>>>,
26 handle: Option<JoinHandle<()>>,
27 running: Arc<RwLock<bool>>,
28}
29
30impl Client {
31 pub fn new(client_config: ClientConfig) -> Self {
41 Self {
42 client_config,
43 namespaces: Arc::new(RwLock::new(HashMap::new())),
44 handle: None,
45 running: Arc::new(RwLock::new(false)),
46 }
47 }
48
49 pub fn namespace(&self, namespace: &str) -> Arc<Cache> {
59 let mut namespaces = self.namespaces.write().unwrap();
60 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
61 trace!("Cache miss, creating cache for namespace {}", namespace);
62 Arc::new(Cache::new(self.client_config.clone(), namespace))
63 });
64 cache.clone()
65 }
66
67 pub fn start(&mut self) -> Result<(), Error> {
68 let mut running = self.running.write().unwrap();
69 if *running {
70 return Err(Error::AlreadyRunning);
71 }
72
73 *running = true;
74
75 let running = self.running.clone();
76 let namespaces = self.namespaces.clone();
77 let handle = thread::spawn(move || {
79 loop {
80 let running = running.read().unwrap();
81 if !*running {
82 break;
83 }
84
85 let namespaces = namespaces.read().unwrap();
86 for (namespace, cache) in namespaces.iter() {
88 if let Err(err) = block_on(cache.refresh()) {
89 log::error!(
90 "Failed to refresh cache for namespace {}: {:?}",
91 namespace,
92 err
93 );
94 } else {
95 log::debug!("Successfully refreshed cache for namespace {}", namespace);
96 }
97 }
98
99 thread::sleep(Duration::from_secs(30));
101 }
102 });
103
104 self.handle = Some(handle);
105
106 Ok(())
107 }
108
109 pub fn stop(&mut self) {
110 let mut running = self.running.write().unwrap();
111 *running = false;
112 if let Some(handle) = self.handle.take() {
113 handle.join().unwrap();
114 }
115 }
116}
117
118impl Drop for Client {
119 fn drop(&mut self) {
120 self.stop();
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use lazy_static::lazy_static;
128 use std::path::PathBuf;
129
130 lazy_static! {
131 static ref CLIENT_NO_SECRET: Client = {
132 let config = ClientConfig {
133 app_id: String::from("101010101"),
134 cluster: String::from("default"),
135 config_server: String::from("http://81.68.181.139:8080"),
136 label: None,
137 secret: None,
138 cache_dir: Some(PathBuf::from("/tmp/apollo")),
139 ip: None,
140 };
141 Client::new(config)
142 };
143 static ref CLIENT_WITH_SECRET: Client = {
144 let config = ClientConfig {
145 app_id: String::from("101010102"),
146 cluster: String::from("default"),
147 config_server: String::from("http://81.68.181.139:8080"),
148 label: None,
149 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
150 cache_dir: Some(PathBuf::from("/tmp/apollo")),
151 ip: None,
152 };
153 Client::new(config)
154 };
155 static ref CLIENT_WITH_GRAYSCALE_IP: Client = {
156 let config = ClientConfig {
157 app_id: String::from("101010101"),
158 cluster: String::from("default"),
159 config_server: String::from("http://81.68.181.139:8080"),
160 label: None,
161 secret: None,
162 cache_dir: Some(PathBuf::from("/tmp/apollo")),
163 ip: Some(String::from("1.2.3.4")),
164 };
165 Client::new(config)
166 };
167 static ref CLIENT_WITH_GRAYSCALE_LABEL: Client = {
168 let config = ClientConfig {
169 app_id: String::from("101010101"),
170 cluster: String::from("default"),
171 config_server: String::from("http://81.68.181.139:8080"),
172 label: Some(String::from("GrayScale")),
173 secret: None,
174 cache_dir: Some(PathBuf::from("/tmp/apollo")),
175 ip: None,
176 };
177 Client::new(config)
178 };
179 }
180
181 pub(crate) fn setup() {
182 let _ = env_logger::builder().is_test(true).try_init();
183 }
184
185 #[tokio::test]
186 async fn test_missing_value() {
187 setup();
188 let cache = CLIENT_NO_SECRET.namespace("application");
189 assert_eq!(cache.get_property::<String>("missingValue").await, None);
190 }
191
192 #[tokio::test]
193 async fn test_string_value() {
194 setup();
195 let cache = CLIENT_NO_SECRET.namespace("application");
196 assert_eq!(
197 cache.get_property::<String>("stringValue").await,
198 Some("string value".to_string())
199 );
200 }
201
202 #[tokio::test]
203 async fn test_string_value_with_secret() {
204 setup();
205 let cache = CLIENT_WITH_SECRET.namespace("application");
206 assert_eq!(
207 cache.get_property::<String>("stringValue").await,
208 Some("string value".to_string())
209 );
210 }
211
212 #[tokio::test]
213 async fn test_int_value() {
214 setup();
215 let cache = CLIENT_NO_SECRET.namespace("application");
216 assert_eq!(cache.get_property::<i32>("intValue").await, Some(42));
217 }
218
219 #[tokio::test]
220 async fn test_int_value_with_secret() {
221 setup();
222 let cache = CLIENT_WITH_SECRET.namespace("application");
223 assert_eq!(cache.get_property::<i32>("intValue").await, Some(42));
224 }
225
226 #[tokio::test]
227 async fn test_float_value() {
228 setup();
229 let cache = CLIENT_NO_SECRET.namespace("application");
230 assert_eq!(cache.get_property::<f64>("floatValue").await, Some(4.20));
231 }
232
233 #[tokio::test]
234 async fn test_float_value_with_secret() {
235 setup();
236 let cache = CLIENT_WITH_SECRET.namespace("application");
237 assert_eq!(cache.get_property::<f64>("floatValue").await, Some(4.20));
238 }
239
240 #[tokio::test]
241 async fn test_bool_value() {
242 setup();
243 let cache = CLIENT_NO_SECRET.namespace("application");
244 assert_eq!(cache.get_property::<bool>("boolValue").await, Some(false));
245 }
246
247 #[tokio::test]
248 async fn test_bool_value_with_secret() {
249 setup();
250 let cache = CLIENT_WITH_SECRET.namespace("application");
251 assert_eq!(cache.get_property::<bool>("boolValue").await, Some(false));
252 }
253
254 #[tokio::test]
255 async fn test_bool_value_with_grayscale_ip() {
256 setup();
257 let cache = CLIENT_WITH_GRAYSCALE_IP.namespace("application");
258 assert_eq!(
259 cache.get_property::<bool>("grayScaleValue").await,
260 Some(true)
261 );
262 let cache = CLIENT_NO_SECRET.namespace("application");
263 assert_eq!(
264 cache.get_property::<bool>("grayScaleValue").await,
265 Some(false)
266 );
267 }
268
269 #[tokio::test]
270 async fn test_bool_value_with_grayscale_label() {
271 setup();
272 let cache = CLIENT_WITH_GRAYSCALE_LABEL.namespace("application");
273 assert_eq!(
274 cache.get_property::<bool>("grayScaleValue").await,
275 Some(true)
276 );
277 let cache = CLIENT_NO_SECRET.namespace("application");
278 assert_eq!(
279 cache.get_property::<bool>("grayScaleValue").await,
280 Some(false)
281 );
282 }
283}