growthbook_sdk_rust/
client.rs

1use 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}