apollo_rust_client/
lib.rs1use async_std::sync::RwLock;
2use cache::Cache;
3use client_config::ClientConfig;
4use log::trace;
5use std::{collections::HashMap, sync::Arc, time::Duration};
6use wasm_bindgen::prelude::wasm_bindgen;
7
8cfg_if::cfg_if! {
9 if #[cfg(target_arch = "wasm32")] {
10 use wasm_bindgen_futures::spawn_local as spawn;
11 } else {
12 use async_std::task::spawn as spawn;
13 }
14}
15
16pub mod cache;
17pub mod client_config;
18
19#[derive(Debug, thiserror::Error)]
21pub enum Error {
22 #[error("Client is already running")]
23 AlreadyRunning,
24}
25
26#[wasm_bindgen]
28pub struct Client {
29 client_config: ClientConfig,
30 namespaces: Arc<RwLock<HashMap<String, Arc<Cache>>>>,
31 handle: Option<async_std::task::JoinHandle<()>>,
32 running: Arc<RwLock<bool>>,
33}
34
35impl Client {
36 pub async fn start(&mut self) -> Result<(), Error> {
37 let mut running = self.running.write().await;
38 if *running {
39 return Err(Error::AlreadyRunning);
40 }
41
42 *running = true;
43
44 let running = self.running.clone();
45 let namespaces = self.namespaces.clone();
46
47 let _handle = spawn(async move {
49 loop {
50 let running = running.read().await;
51 if !*running {
52 break;
53 }
54
55 let namespaces = namespaces.read().await;
56 for (namespace, cache) in namespaces.iter() {
58 if let Err(err) = cache.refresh().await {
59 log::error!(
60 "Failed to refresh cache for namespace {}: {:?}",
61 namespace,
62 err
63 );
64 } else {
65 log::debug!("Successfully refreshed cache for namespace {}", namespace);
66 }
67 }
68
69 async_std::task::sleep(Duration::from_secs(30)).await;
71 }
72 });
73
74 cfg_if::cfg_if! {
75 if #[cfg(target_arch = "wasm32")] {
76 self.handle = None;
77 } else {
78 self.handle = Some(_handle);
79 }
80 }
81
82 Ok(())
83 }
84
85 pub async fn stop(&mut self) {
86 let mut running = self.running.write().await;
87 *running = false;
88
89 cfg_if::cfg_if! {
90 if #[cfg(not(target_arch = "wasm32"))] {
91 if let Some(handle) = self.handle.take() {
92 handle.cancel().await;
93 }
94 }
95 }
96 }
97}
98
99cfg_if::cfg_if! {
100 if #[cfg(target_arch = "wasm32")] {
101 #[wasm_bindgen]
102 impl Client {
103 pub async fn namespace(&self, namespace: &str) -> Cache {
113 let mut namespaces = self.namespaces.write().await;
114 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
115 trace!("Cache miss, creating cache for namespace {}", namespace);
116 Arc::new(Cache::new(self.client_config.clone(), namespace))
117 });
118 let cache = (*cache).clone();
119 (*cache).to_owned()
120 }
121 }
122 } else {
123 impl Client {
124 pub async fn namespace(&self, namespace: &str) -> Arc<Cache> {
134 let mut namespaces = self.namespaces.write().await;
135 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
136 trace!("Cache miss, creating cache for namespace {}", namespace);
137 Arc::new(Cache::new(self.client_config.clone(), namespace))
138 });
139 cache.clone()
140 }
141 }
142 }
143}
144
145#[wasm_bindgen]
146impl Client {
147 #[wasm_bindgen(constructor)]
157 pub fn new(client_config: ClientConfig) -> Self {
158 Self {
159 client_config,
160 namespaces: Arc::new(RwLock::new(HashMap::new())),
161 handle: None,
162 running: Arc::new(RwLock::new(false)),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use lazy_static::lazy_static;
171 use wasm_bindgen_test::wasm_bindgen_test;
172
173 lazy_static! {
174 static ref CLIENT_NO_SECRET: Client = {
175 let config = ClientConfig {
176 app_id: String::from("101010101"),
177 cluster: String::from("default"),
178 config_server: String::from("http://81.68.181.139:8080"),
179 label: None,
180 secret: None,
181 cache_dir: Some(String::from("/tmp/apollo")),
182 ip: None,
183 };
184 Client::new(config)
185 };
186 static ref CLIENT_WITH_SECRET: Client = {
187 let config = ClientConfig {
188 app_id: String::from("101010102"),
189 cluster: String::from("default"),
190 config_server: String::from("http://81.68.181.139:8080"),
191 label: None,
192 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
193 cache_dir: Some(String::from("/tmp/apollo")),
194 ip: None,
195 };
196 Client::new(config)
197 };
198 static ref CLIENT_WITH_GRAYSCALE_IP: Client = {
199 let config = ClientConfig {
200 app_id: String::from("101010101"),
201 cluster: String::from("default"),
202 config_server: String::from("http://81.68.181.139:8080"),
203 label: None,
204 secret: None,
205 cache_dir: Some(String::from("/tmp/apollo")),
206 ip: Some(String::from("1.2.3.4")),
207 };
208 Client::new(config)
209 };
210 static ref CLIENT_WITH_GRAYSCALE_LABEL: Client = {
211 let config = ClientConfig {
212 app_id: String::from("101010101"),
213 cluster: String::from("default"),
214 config_server: String::from("http://81.68.181.139:8080"),
215 label: Some(String::from("GrayScale")),
216 secret: None,
217 cache_dir: Some(String::from("/tmp/apollo")),
218 ip: None,
219 };
220 Client::new(config)
221 };
222 }
223
224 pub(crate) fn setup() {
225 cfg_if::cfg_if! {
226 if #[cfg(target_arch = "wasm32")] {
227 let _ = wasm_logger::init(wasm_logger::Config::default());
228 console_error_panic_hook::set_once();
229 } else {
230 let _ = env_logger::builder().is_test(true).try_init();
231 }
232 }
233 }
234
235 #[tokio::test]
236 async fn test_missing_value() {
237 setup();
238 let cache = CLIENT_NO_SECRET.namespace("application");
239 assert_eq!(
240 cache.await.get_property::<String>("missingValue").await,
241 None
242 );
243 }
244
245 #[wasm_bindgen_test]
246 #[allow(dead_code)]
247 async fn test_missing_value_wasm() {
248 setup();
249 let cache = CLIENT_NO_SECRET.namespace("application");
250 assert_eq!(
251 cache.await.get_property::<String>("missingValue").await,
252 None
253 );
254 }
255
256 #[tokio::test]
257 async fn test_string_value() {
258 setup();
259 let cache = CLIENT_NO_SECRET.namespace("application");
260 assert_eq!(
261 cache.await.get_property::<String>("stringValue").await,
262 Some("string value".to_string())
263 );
264 }
265
266 #[wasm_bindgen_test]
267 #[allow(dead_code)]
268 async fn test_string_value_wasm() {
269 setup();
270 let cache = CLIENT_NO_SECRET.namespace("application");
271 assert_eq!(
272 cache.await.get_property::<String>("stringValue").await,
273 Some("string value".to_string())
274 );
275 }
276
277 #[tokio::test]
278 async fn test_string_value_with_secret() {
279 setup();
280 console_error_panic_hook::set_once();
281 let cache = CLIENT_WITH_SECRET.namespace("application");
282 assert_eq!(
283 cache.await.get_property::<String>("stringValue").await,
284 Some("string value".to_string())
285 );
286 }
287
288 #[wasm_bindgen_test]
289 #[allow(dead_code)]
290 async fn test_string_value_with_secret_wasm() {
291 setup();
292 let cache = CLIENT_WITH_SECRET.namespace("application");
293 assert_eq!(
294 cache.await.get_property::<String>("stringValue").await,
295 Some("string value".to_string())
296 );
297 }
298
299 #[tokio::test]
300 async fn test_int_value() {
301 setup();
302 let cache = CLIENT_NO_SECRET.namespace("application");
303 assert_eq!(cache.await.get_property::<i32>("intValue").await, Some(42));
304 }
305
306 #[wasm_bindgen_test]
307 #[allow(dead_code)]
308 async fn test_int_value_wasm() {
309 setup();
310 let cache = CLIENT_NO_SECRET.namespace("application");
311 assert_eq!(cache.await.get_property::<i32>("intValue").await, Some(42));
312 }
313
314 #[tokio::test]
315 async fn test_int_value_with_secret() {
316 setup();
317 let cache = CLIENT_WITH_SECRET.namespace("application");
318 assert_eq!(cache.await.get_property::<i32>("intValue").await, Some(42));
319 }
320
321 #[wasm_bindgen_test]
322 #[allow(dead_code)]
323 async fn test_int_value_with_secret_wasm() {
324 setup();
325 let cache = CLIENT_WITH_SECRET.namespace("application");
326 assert_eq!(cache.await.get_property::<i32>("intValue").await, Some(42));
327 }
328
329 #[tokio::test]
330 async fn test_float_value() {
331 setup();
332 let cache = CLIENT_NO_SECRET.namespace("application");
333 assert_eq!(
334 cache.await.get_property::<f64>("floatValue").await,
335 Some(4.20)
336 );
337 }
338
339 #[wasm_bindgen_test]
340 #[allow(dead_code)]
341 async fn test_float_value_wasm() {
342 setup();
343 let cache = CLIENT_NO_SECRET.namespace("application");
344 assert_eq!(
345 cache.await.get_property::<f64>("floatValue").await,
346 Some(4.20)
347 );
348 }
349
350 #[tokio::test]
351 async fn test_float_value_with_secret() {
352 setup();
353 let cache = CLIENT_WITH_SECRET.namespace("application");
354 assert_eq!(
355 cache.await.get_property::<f64>("floatValue").await,
356 Some(4.20)
357 );
358 }
359
360 #[wasm_bindgen_test]
361 #[allow(dead_code)]
362 async fn test_float_value_with_secret_wasm() {
363 setup();
364 let cache = CLIENT_WITH_SECRET.namespace("application");
365 assert_eq!(
366 cache.await.get_property::<f64>("floatValue").await,
367 Some(4.20)
368 );
369 }
370
371 #[tokio::test]
372 async fn test_bool_value() {
373 setup();
374 let cache = CLIENT_NO_SECRET.namespace("application");
375 assert_eq!(
376 cache.await.get_property::<bool>("boolValue").await,
377 Some(false)
378 );
379 }
380
381 #[wasm_bindgen_test]
382 #[allow(dead_code)]
383 async fn test_bool_value_wasm() {
384 setup();
385 let cache = CLIENT_NO_SECRET.namespace("application");
386 assert_eq!(
387 cache.await.get_property::<bool>("boolValue").await,
388 Some(false)
389 );
390 }
391
392 #[tokio::test]
393 async fn test_bool_value_with_secret() {
394 setup();
395 let cache = CLIENT_WITH_SECRET.namespace("application");
396 assert_eq!(
397 cache.await.get_property::<bool>("boolValue").await,
398 Some(false)
399 );
400 }
401
402 #[wasm_bindgen_test]
403 #[allow(dead_code)]
404 async fn test_bool_value_with_secret_wasm() {
405 setup();
406 let cache = CLIENT_WITH_SECRET.namespace("application");
407 assert_eq!(
408 cache.await.get_property::<bool>("boolValue").await,
409 Some(false)
410 );
411 }
412
413 #[tokio::test]
414 async fn test_bool_value_with_grayscale_ip() {
415 setup();
416 let cache = CLIENT_WITH_GRAYSCALE_IP.namespace("application");
417 assert_eq!(
418 cache.await.get_property::<bool>("grayScaleValue").await,
419 Some(true)
420 );
421 let cache = CLIENT_NO_SECRET.namespace("application");
422 assert_eq!(
423 cache.await.get_property::<bool>("grayScaleValue").await,
424 Some(false)
425 );
426 }
427
428 #[wasm_bindgen_test]
429 #[allow(dead_code)]
430 async fn test_bool_value_with_grayscale_ip_wasm() {
431 setup();
432 let cache = CLIENT_WITH_GRAYSCALE_IP.namespace("application");
433 assert_eq!(
434 cache.await.get_property::<bool>("grayScaleValue").await,
435 Some(true)
436 );
437
438 let cache = CLIENT_NO_SECRET.namespace("application");
439 assert_eq!(
440 cache.await.get_property::<bool>("grayScaleValue").await,
441 Some(false)
442 );
443 }
444
445 #[tokio::test]
446 async fn test_bool_value_with_grayscale_label() {
447 setup();
448 let cache = CLIENT_WITH_GRAYSCALE_LABEL.namespace("application").await;
449 assert_eq!(
450 cache.get_property::<bool>("grayScaleValue").await,
451 Some(true)
452 );
453 let cache = CLIENT_NO_SECRET.namespace("application").await;
454 assert_eq!(
455 cache.get_property::<bool>("grayScaleValue").await,
456 Some(false)
457 );
458 }
459
460 #[wasm_bindgen_test]
461 #[allow(dead_code)]
462 async fn test_bool_value_with_grayscale_label_wasm() {
463 setup();
464 let cache = CLIENT_WITH_GRAYSCALE_LABEL.namespace("application").await;
465 assert_eq!(
466 cache.get_property::<bool>("grayScaleValue").await,
467 Some(true)
468 );
469
470 let cache = CLIENT_NO_SECRET.namespace("application").await;
471 assert_eq!(
472 cache.get_property::<bool>("grayScaleValue").await,
473 Some(false)
474 );
475 }
476}