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};
11use wasm_bindgen::prelude::wasm_bindgen;
12
13pub mod cache;
14pub mod client_config;
15
16#[derive(Debug, thiserror::Error)]
18pub enum Error {
19 #[error("Client is already running")]
20 AlreadyRunning,
21}
22
23#[wasm_bindgen]
25pub struct Client {
26 client_config: ClientConfig,
27 namespaces: Arc<RwLock<HashMap<String, Arc<Cache>>>>,
28 handle: Option<JoinHandle<()>>,
29 running: Arc<RwLock<bool>>,
30}
31
32impl Client {
33 pub fn start(&mut self) -> Result<(), Error> {
34 let mut running = self.running.write().unwrap();
35 if *running {
36 return Err(Error::AlreadyRunning);
37 }
38
39 *running = true;
40
41 let running = self.running.clone();
42 let namespaces = self.namespaces.clone();
43 let handle = thread::spawn(move || {
45 loop {
46 let running = running.read().unwrap();
47 if !*running {
48 break;
49 }
50
51 let namespaces = namespaces.read().unwrap();
52 for (namespace, cache) in namespaces.iter() {
54 if let Err(err) = block_on(cache.refresh()) {
55 log::error!(
56 "Failed to refresh cache for namespace {}: {:?}",
57 namespace,
58 err
59 );
60 } else {
61 log::debug!("Successfully refreshed cache for namespace {}", namespace);
62 }
63 }
64
65 thread::sleep(Duration::from_secs(30));
67 }
68 });
69
70 self.handle = Some(handle);
71
72 Ok(())
73 }
74
75 pub fn stop(&mut self) {
76 let mut running = self.running.write().unwrap();
77 *running = false;
78 if let Some(handle) = self.handle.take() {
79 handle.join().unwrap();
80 }
81 }
82}
83
84cfg_if::cfg_if! {
85 if #[cfg(target_arch = "wasm32")] {
86 #[wasm_bindgen]
87 impl Client {
88 pub fn namespace(&self, namespace: &str) -> Cache {
98 let mut namespaces = self.namespaces.write().unwrap();
99 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
100 trace!("Cache miss, creating cache for namespace {}", namespace);
101 Arc::new(Cache::new(self.client_config.clone(), namespace))
102 });
103 let cache = (*cache).clone();
104 (*cache).to_owned()
105 }
106 }
107 } else {
108 impl Client {
109 pub fn namespace(&self, namespace: &str) -> Arc<Cache> {
119 let mut namespaces = self.namespaces.write().unwrap();
120 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
121 trace!("Cache miss, creating cache for namespace {}", namespace);
122 Arc::new(Cache::new(self.client_config.clone(), namespace))
123 });
124 cache.clone()
125 }
126 }
127 }
128}
129
130#[wasm_bindgen]
131impl Client {
132 #[wasm_bindgen(constructor)]
142 pub fn new(client_config: ClientConfig) -> Self {
143 Self {
144 client_config,
145 namespaces: Arc::new(RwLock::new(HashMap::new())),
146 handle: None,
147 running: Arc::new(RwLock::new(false)),
148 }
149 }
150}
151
152impl Drop for Client {
153 fn drop(&mut self) {
154 self.stop();
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use lazy_static::lazy_static;
162 use wasm_bindgen_test::wasm_bindgen_test;
163
164 lazy_static! {
165 static ref CLIENT_NO_SECRET: Client = {
166 let config = ClientConfig {
167 app_id: String::from("101010101"),
168 cluster: String::from("default"),
169 config_server: String::from("http://81.68.181.139:8080"),
170 label: None,
171 secret: None,
172 cache_dir: Some(String::from("/tmp/apollo")),
173 ip: None,
174 };
175 Client::new(config)
176 };
177 static ref CLIENT_WITH_SECRET: Client = {
178 let config = ClientConfig {
179 app_id: String::from("101010102"),
180 cluster: String::from("default"),
181 config_server: String::from("http://81.68.181.139:8080"),
182 label: None,
183 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
184 cache_dir: Some(String::from("/tmp/apollo")),
185 ip: None,
186 };
187 Client::new(config)
188 };
189 static ref CLIENT_WITH_GRAYSCALE_IP: Client = {
190 let config = ClientConfig {
191 app_id: String::from("101010101"),
192 cluster: String::from("default"),
193 config_server: String::from("http://81.68.181.139:8080"),
194 label: None,
195 secret: None,
196 cache_dir: Some(String::from("/tmp/apollo")),
197 ip: Some(String::from("1.2.3.4")),
198 };
199 Client::new(config)
200 };
201 static ref CLIENT_WITH_GRAYSCALE_LABEL: Client = {
202 let config = ClientConfig {
203 app_id: String::from("101010101"),
204 cluster: String::from("default"),
205 config_server: String::from("http://81.68.181.139:8080"),
206 label: Some(String::from("GrayScale")),
207 secret: None,
208 cache_dir: Some(String::from("/tmp/apollo")),
209 ip: None,
210 };
211 Client::new(config)
212 };
213 }
214
215 pub(crate) fn setup() {
216 cfg_if::cfg_if! {
217 if #[cfg(target_arch = "wasm32")] {
218 let _ = wasm_logger::init(wasm_logger::Config::default());
219 console_error_panic_hook::set_once();
220 } else {
221 let _ = env_logger::builder().is_test(true).try_init();
222 }
223 }
224 }
225
226 #[tokio::test]
227 async fn test_missing_value() {
228 setup();
229 let cache = CLIENT_NO_SECRET.namespace("application");
230 assert_eq!(cache.get_property::<String>("missingValue").await, None);
231 }
232
233 #[wasm_bindgen_test]
234 #[allow(dead_code)]
235 async fn test_missing_value_wasm() {
236 setup();
237 let cache = CLIENT_NO_SECRET.namespace("application");
238 assert_eq!(cache.get_property::<String>("missingValue").await, None);
239 }
240
241 #[tokio::test]
242 async fn test_string_value() {
243 setup();
244 let cache = CLIENT_NO_SECRET.namespace("application");
245 assert_eq!(
246 cache.get_string("stringValue").await,
247 Some("string value".to_string())
248 );
249 }
250
251 #[wasm_bindgen_test]
252 #[allow(dead_code)]
253 async fn test_string_value_wasm() {
254 setup();
255 let cache = CLIENT_NO_SECRET.namespace("application");
256 assert_eq!(
257 cache.get_string("stringValue").await,
258 Some("string value".to_string())
259 );
260 }
261
262 #[tokio::test]
263 async fn test_string_value_with_secret() {
264 setup();
265 console_error_panic_hook::set_once();
266 let cache = CLIENT_WITH_SECRET.namespace("application");
267 assert_eq!(
268 cache.get_property::<String>("stringValue").await,
269 Some("string value".to_string())
270 );
271 }
272
273 #[wasm_bindgen_test]
274 #[allow(dead_code)]
275 async fn test_string_value_with_secret_wasm() {
276 setup();
277 let cache = CLIENT_WITH_SECRET.namespace("application");
278 assert_eq!(
279 cache.get_string("stringValue").await,
280 Some("string value".to_string())
281 );
282 }
283
284 #[tokio::test]
285 async fn test_int_value() {
286 setup();
287 let cache = CLIENT_NO_SECRET.namespace("application");
288 assert_eq!(cache.get_property::<i32>("intValue").await, Some(42));
289 }
290
291 #[wasm_bindgen_test]
292 #[allow(dead_code)]
293 async fn test_int_value_wasm() {
294 setup();
295 let cache = CLIENT_NO_SECRET.namespace("application");
296 assert_eq!(cache.get_int("intValue").await, Some(42));
297 }
298
299 #[tokio::test]
300 async fn test_int_value_with_secret() {
301 setup();
302 let cache = CLIENT_WITH_SECRET.namespace("application");
303 assert_eq!(cache.get_property::<i32>("intValue").await, Some(42));
304 }
305
306 #[wasm_bindgen_test]
307 #[allow(dead_code)]
308 async fn test_int_value_with_secret_wasm() {
309 setup();
310 let cache = CLIENT_WITH_SECRET.namespace("application");
311 assert_eq!(cache.get_int("intValue").await, Some(42));
312 }
313
314 #[tokio::test]
315 async fn test_float_value() {
316 setup();
317 let cache = CLIENT_NO_SECRET.namespace("application");
318 assert_eq!(cache.get_property::<f64>("floatValue").await, Some(4.20));
319 }
320
321 #[wasm_bindgen_test]
322 #[allow(dead_code)]
323 async fn test_float_value_wasm() {
324 setup();
325 let cache = CLIENT_NO_SECRET.namespace("application");
326 assert_eq!(cache.get_float("floatValue").await, Some(4.20));
327 }
328
329 #[tokio::test]
330 async fn test_float_value_with_secret() {
331 setup();
332 let cache = CLIENT_WITH_SECRET.namespace("application");
333 assert_eq!(cache.get_property::<f64>("floatValue").await, Some(4.20));
334 }
335
336 #[wasm_bindgen_test]
337 #[allow(dead_code)]
338 async fn test_float_value_with_secret_wasm() {
339 setup();
340 let cache = CLIENT_WITH_SECRET.namespace("application");
341 assert_eq!(cache.get_float("floatValue").await, Some(4.20));
342 }
343
344 #[tokio::test]
345 async fn test_bool_value() {
346 setup();
347 let cache = CLIENT_NO_SECRET.namespace("application");
348 assert_eq!(cache.get_property::<bool>("boolValue").await, Some(false));
349 }
350
351 #[wasm_bindgen_test]
352 #[allow(dead_code)]
353 async fn test_bool_value_wasm() {
354 setup();
355 let cache = CLIENT_NO_SECRET.namespace("application");
356 assert_eq!(cache.get_bool("boolValue").await, Some(false));
357 }
358
359 #[tokio::test]
360 async fn test_bool_value_with_secret() {
361 setup();
362 let cache = CLIENT_WITH_SECRET.namespace("application");
363 assert_eq!(cache.get_property::<bool>("boolValue").await, Some(false));
364 }
365
366 #[wasm_bindgen_test]
367 #[allow(dead_code)]
368 async fn test_bool_value_with_secret_wasm() {
369 setup();
370 let cache = CLIENT_WITH_SECRET.namespace("application");
371 assert_eq!(cache.get_bool("boolValue").await, Some(false));
372 }
373
374 #[tokio::test]
375 async fn test_bool_value_with_grayscale_ip() {
376 setup();
377 let cache = CLIENT_WITH_GRAYSCALE_IP.namespace("application");
378 assert_eq!(
379 cache.get_property::<bool>("grayScaleValue").await,
380 Some(true)
381 );
382 let cache = CLIENT_NO_SECRET.namespace("application");
383 assert_eq!(
384 cache.get_property::<bool>("grayScaleValue").await,
385 Some(false)
386 );
387 }
388
389 #[wasm_bindgen_test]
390 #[allow(dead_code)]
391 async fn test_bool_value_with_grayscale_ip_wasm() {
392 setup();
393 let cache = CLIENT_WITH_GRAYSCALE_IP.namespace("application");
394 assert_eq!(cache.get_bool("grayScaleValue").await, Some(true));
395
396 let cache = CLIENT_NO_SECRET.namespace("application");
397 assert_eq!(cache.get_bool("grayScaleValue").await, Some(false));
398 }
399
400 #[tokio::test]
401 async fn test_bool_value_with_grayscale_label() {
402 setup();
403 let cache = CLIENT_WITH_GRAYSCALE_LABEL.namespace("application");
404 assert_eq!(
405 cache.get_property::<bool>("grayScaleValue").await,
406 Some(true)
407 );
408 let cache = CLIENT_NO_SECRET.namespace("application");
409 assert_eq!(
410 cache.get_property::<bool>("grayScaleValue").await,
411 Some(false)
412 );
413 }
414
415 #[wasm_bindgen_test]
416 #[allow(dead_code)]
417 async fn test_bool_value_with_grayscale_label_wasm() {
418 setup();
419 let cache = CLIENT_WITH_GRAYSCALE_LABEL.namespace("application");
420 assert_eq!(cache.get_bool("grayScaleValue").await, Some(true));
421
422 let cache = CLIENT_NO_SECRET.namespace("application");
423 assert_eq!(cache.get_bool("grayScaleValue").await, Some(false));
424 }
425}