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    Network,
18    Bootstrap,
19    Adapter(String),
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            "Network" => SpecsSource::Network,
56            "Bootstrap" => SpecsSource::Bootstrap,
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}
108
109#[repr(C)]
110#[derive(Serialize, Deserialize, Debug, Clone)]
111pub struct SpecsInfo {
112    pub lcut: Option<u64>,
113    pub checksum: Option<String>,
114    pub zstd_dict_id: Option<String>,
115    pub source: SpecsSource,
116}
117
118impl SpecsInfo {
119    #[must_use]
120    pub fn empty() -> Self {
121        Self {
122            lcut: None,
123            checksum: None,
124            zstd_dict_id: None,
125            source: SpecsSource::NoValues,
126        }
127    }
128
129    #[must_use]
130    pub fn error() -> Self {
131        Self {
132            lcut: None,
133            checksum: None,
134            zstd_dict_id: None,
135            source: SpecsSource::Error,
136        }
137    }
138}
139
140pub trait SpecsUpdateListener: Send + Sync {
141    fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;
142
143    fn get_current_specs_info(&self) -> SpecsInfo;
144}
145
146impl fmt::Debug for dyn SpecsAdapter {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        write!(f, "{}", self.get_type_name())
149    }
150}