growthbook_sdk_rust/
client.rs1use std::collections::HashMap;
2use std::sync::{Arc, RwLock};
3use std::time::Duration;
4
5use tokio::time::sleep;
6use tracing::error;
7
8use crate::env::Environment;
9use crate::error::GrowthbookError;
10use crate::gateway::GrowthbookGateway;
11use crate::growthbook::GrowthBook;
12use crate::model_private::FeatureResult;
13use crate::model_public::GrowthBookAttribute;
14
15#[derive(Clone)]
16pub struct GrowthBookClient {
17 pub gb: Arc<RwLock<GrowthBook>>,
18}
19
20async fn updated_features_task(
21 growthbook_gateway: GrowthbookGateway,
22 config: Arc<RwLock<GrowthBook>>,
23 interval: Duration,
24) {
25 loop {
26 match growthbook_gateway.get_features(None).await {
27 Ok(new_config) => {
28 let mut writable_config = config.write().expect("problem to create mutex for gb data");
29 let updated_features = GrowthBook {
30 forced_variations: new_config.forced_variations,
31 features: new_config.features,
32 };
33 *writable_config = updated_features;
34 },
35 Err(e) => {
36 error!("[growthbook-sdk] Failed to fetch features from server: {:?}", e);
37 },
38 }
39 sleep(interval).await;
40 }
41}
42
43impl GrowthBookClient {
44 pub async fn new(
45 api_url: &str,
46 sdk_key: &str,
47 update_interval: Option<Duration>,
48 http_timeout: Option<Duration>,
49 ) -> Result<Self, GrowthbookError> {
50 let default_interval = update_interval.unwrap_or_else(|| {
51 let seconds = Environment::u64_or_default("GB_UPDATE_INTERVAL", 60);
52 Duration::from_secs(seconds)
53 });
54 let default_timeout = http_timeout.unwrap_or_else(|| {
55 let seconds = Environment::u64_or_default("GB_HTTP_CLIENT_TIMEOUT", 10);
56 Duration::from_secs(seconds)
57 });
58 let gb_gateway = GrowthbookGateway::new(api_url, sdk_key, default_timeout)?;
59 let resp = gb_gateway.get_features(None).await?;
60 let growthbook_writable = Arc::new(RwLock::new(GrowthBook {
61 forced_variations: resp.forced_variations,
62 features: resp.features,
63 }));
64 let gb_rw_clone = Arc::clone(&growthbook_writable);
65
66 tokio::spawn(async move {
67 updated_features_task(gb_gateway, gb_rw_clone, default_interval).await;
68 });
69
70 Ok(GrowthBookClient { gb: growthbook_writable })
71 }
72
73 pub fn is_on(
74 &self,
75 feature_name: &str,
76 user_attributes: Option<Vec<GrowthBookAttribute>>,
77 ) -> bool {
78 self.read_gb().check(feature_name, &user_attributes).on
79 }
80
81 pub fn is_off(
82 &self,
83 feature_name: &str,
84 user_attributes: Option<Vec<GrowthBookAttribute>>,
85 ) -> bool {
86 self.read_gb().check(feature_name, &user_attributes).off
87 }
88
89 pub fn feature_result(
90 &self,
91 feature_name: &str,
92 user_attributes: Option<Vec<GrowthBookAttribute>>,
93 ) -> FeatureResult {
94 self.read_gb().check(feature_name, &user_attributes)
95 }
96
97 pub fn total_features(&self) -> usize {
98 let gb_data = self.read_gb();
99 gb_data.features.len()
100 }
101
102 fn read_gb(&self) -> GrowthBook {
103 match self.gb.read() {
104 Ok(rw_read_guard) => (*rw_read_guard).clone(),
105 Err(e) => {
106 error!("{}", format!("[growthbook-sdk] problem to reading gb mutex data returning empty {:?}", e));
107 GrowthBook {
108 forced_variations: None,
109 features: HashMap::new(),
110 }
111 },
112 }
113 }
114}