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
22impl SpecsSource {
23    pub fn new_from_string(s: &str) -> Self {
24        if s.starts_with("Adapter(") {
25            let name = s
26                .strip_prefix("Adapter(")
27                .and_then(|s| s.strip_suffix(")"))
28                .unwrap_or("");
29            return SpecsSource::Adapter(name.to_string());
30        }
31
32        match s {
33            "Uninitialized" => SpecsSource::Uninitialized,
34            "NoValues" => SpecsSource::NoValues,
35            "Error" => SpecsSource::Error,
36            "Loading" => SpecsSource::Loading,
37            "Network" => SpecsSource::Network,
38            "Bootstrap" => SpecsSource::Bootstrap,
39            _ => SpecsSource::Error,
40        }
41    }
42}
43
44impl fmt::Display for SpecsSource {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        let s = match self {
47            SpecsSource::Adapter(name) => {
48                let type_name = format!("Adapter({name})");
49                type_name
50            }
51            SpecsSource::Uninitialized => "Uninitialized".to_string(),
52            SpecsSource::NoValues => "NoValues".to_string(),
53            SpecsSource::Error => "Error".to_string(),
54            SpecsSource::Loading => "Loading".to_string(),
55            SpecsSource::Network => "Network".to_string(),
56            SpecsSource::Bootstrap => "Bootstrap".to_string(),
57        };
58        write!(f, "{s}")
59    }
60}
61
62#[async_trait]
63pub trait SpecsAdapter: Send + Sync {
64    fn initialize(&self, listener: Arc<dyn SpecsUpdateListener>);
65
66    async fn start(
67        self: Arc<Self>,
68        statsig_runtime: &Arc<StatsigRuntime>,
69    ) -> Result<(), StatsigErr>;
70
71    async fn shutdown(
72        &self,
73        timeout: Duration,
74        statsig_runtime: &Arc<StatsigRuntime>,
75    ) -> Result<(), StatsigErr>;
76
77    async fn schedule_background_sync(
78        self: Arc<Self>,
79        statsig_runtime: &Arc<StatsigRuntime>,
80    ) -> Result<(), StatsigErr>;
81
82    fn get_type_name(&self) -> String;
83}
84
85pub struct SpecsUpdate {
86    pub data: String,
87    pub source: SpecsSource,
88    pub received_at: u64,
89}
90
91#[repr(C)]
92#[derive(Serialize, Deserialize, Debug)]
93pub struct SpecsInfo {
94    pub lcut: Option<u64>,
95    pub checksum: Option<String>,
96    pub zstd_dict_id: Option<String>,
97    pub source: SpecsSource,
98}
99
100impl SpecsInfo {
101    #[must_use]
102    pub fn empty() -> Self {
103        Self {
104            lcut: None,
105            checksum: None,
106            zstd_dict_id: None,
107            source: SpecsSource::NoValues,
108        }
109    }
110
111    #[must_use]
112    pub fn error() -> Self {
113        Self {
114            lcut: None,
115            checksum: None,
116            zstd_dict_id: None,
117            source: SpecsSource::Error,
118        }
119    }
120}
121
122pub trait SpecsUpdateListener: Send + Sync {
123    fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;
124
125    fn get_current_specs_info(&self) -> SpecsInfo;
126}
127
128impl fmt::Debug for dyn SpecsAdapter {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{}", self.get_type_name())
131    }
132}