statsig_rust/specs_adapter/
specs_adapter_trait.rs

1use crate::statsig_err::StatsigErr;
2use crate::StatsigRuntime;
3use async_trait::async_trait;
4use serde::Deserialize;
5use serde::Serialize;
6use std::fmt::{self, Debug};
7use std::sync::Arc;
8use std::time::Duration;
9
10#[repr(u8)]
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub enum SpecsSource {
13    Uninitialized = 0,
14    NoValues,
15    Error,
16    Loading,
17    Bootstrap,
18    Adapter(String),
19    Network,
20}
21
22const DEFAULT_CONFIG_COMPRESSION_MODE: ConfigCompressionMode = ConfigCompressionMode::Gzip;
23
24#[derive(Clone)]
25pub enum ConfigCompressionMode {
26    Gzip,
27    Dictionary,
28}
29
30impl From<&str> for ConfigCompressionMode {
31    fn from(s: &str) -> Self {
32        match s.to_lowercase().as_str() {
33            "gzip" => ConfigCompressionMode::Gzip,
34            "dictionary" => ConfigCompressionMode::Dictionary,
35            _ => DEFAULT_CONFIG_COMPRESSION_MODE,
36        }
37    }
38}
39
40impl SpecsSource {
41    pub fn new_from_string(s: &str) -> Self {
42        if s.starts_with("Adapter(") {
43            let name = s
44                .strip_prefix("Adapter(")
45                .and_then(|s| s.strip_suffix(")"))
46                .unwrap_or("");
47            return SpecsSource::Adapter(name.to_string());
48        }
49
50        match s {
51            "Uninitialized" => SpecsSource::Uninitialized,
52            "NoValues" => SpecsSource::NoValues,
53            "Error" => SpecsSource::Error,
54            "Loading" => SpecsSource::Loading,
55            "Bootstrap" => SpecsSource::Bootstrap,
56            "Network" => SpecsSource::Network,
57            _ => SpecsSource::Error,
58        }
59    }
60}
61
62impl fmt::Display for SpecsSource {
63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64        let s = match self {
65            SpecsSource::Adapter(name) => {
66                let type_name = format!("Adapter({name})");
67                type_name
68            }
69            SpecsSource::Uninitialized => "Uninitialized".to_string(),
70            SpecsSource::NoValues => "NoValues".to_string(),
71            SpecsSource::Error => "Error".to_string(),
72            SpecsSource::Loading => "Loading".to_string(),
73            SpecsSource::Network => "Network".to_string(),
74            SpecsSource::Bootstrap => "Bootstrap".to_string(),
75        };
76        write!(f, "{s}")
77    }
78}
79
80#[async_trait]
81pub trait SpecsAdapter: Send + Sync {
82    fn initialize(&self, listener: Arc<dyn SpecsUpdateListener>);
83
84    async fn start(
85        self: Arc<Self>,
86        statsig_runtime: &Arc<StatsigRuntime>,
87    ) -> Result<(), StatsigErr>;
88
89    async fn shutdown(
90        &self,
91        timeout: Duration,
92        statsig_runtime: &Arc<StatsigRuntime>,
93    ) -> Result<(), StatsigErr>;
94
95    async fn schedule_background_sync(
96        self: Arc<Self>,
97        statsig_runtime: &Arc<StatsigRuntime>,
98    ) -> Result<(), StatsigErr>;
99
100    fn get_type_name(&self) -> String;
101}
102
103pub struct SpecsUpdate {
104    pub data: Vec<u8>,
105    pub source: SpecsSource,
106    pub received_at: u64,
107    pub source_api: Option<String>,
108}
109
110#[repr(C)]
111#[derive(Serialize, Deserialize, Debug, Clone)]
112pub struct SpecsInfo {
113    pub lcut: Option<u64>,
114    pub checksum: Option<String>,
115    pub zstd_dict_id: Option<String>,
116    pub source: SpecsSource,
117    pub source_api: Option<String>,
118}
119
120impl SpecsInfo {
121    #[must_use]
122    pub fn empty() -> Self {
123        Self {
124            lcut: None,
125            checksum: None,
126            zstd_dict_id: None,
127            source: SpecsSource::NoValues,
128            source_api: None,
129        }
130    }
131
132    #[must_use]
133    pub fn error() -> Self {
134        Self {
135            lcut: None,
136            checksum: None,
137            zstd_dict_id: None,
138            source: SpecsSource::Error,
139            source_api: None,
140        }
141    }
142}
143
144pub trait SpecsUpdateListener: Send + Sync {
145    fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;
146
147    fn get_current_specs_info(&self) -> SpecsInfo;
148}
149
150impl fmt::Debug for dyn SpecsAdapter {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        write!(f, "{}", self.get_type_name())
153    }
154}