statsig_rust/specs_adapter/
specs_adapter_trait.rs

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