Skip to main content

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 enum SpecsFormat {
105    Json,
106    Protobuf,
107}
108
109impl From<&SpecsFormat> for &str {
110    fn from(val: &SpecsFormat) -> Self {
111        match val {
112            SpecsFormat::Json => "JSON",
113            SpecsFormat::Protobuf => "Protobuf",
114        }
115    }
116}
117
118pub struct SpecsUpdate {
119    pub data: ResponseData,
120    pub source: SpecsSource,
121    pub received_at: u64,
122    pub source_api: Option<String>,
123    pub has_updates: Option<bool>,
124}
125
126#[repr(C)]
127#[derive(Serialize, Deserialize, Debug, Clone)]
128pub struct SpecsInfo {
129    pub lcut: Option<u64>,
130    pub checksum: Option<String>,
131    pub source: SpecsSource,
132    pub source_api: Option<String>,
133}
134
135impl SpecsInfo {
136    #[must_use]
137    pub fn empty() -> Self {
138        Self {
139            lcut: None,
140            checksum: None,
141            source: SpecsSource::NoValues,
142            source_api: None,
143        }
144    }
145
146    #[must_use]
147    pub fn error() -> Self {
148        Self {
149            lcut: None,
150            checksum: None,
151            source: SpecsSource::Error,
152            source_api: None,
153        }
154    }
155}
156
157pub trait SpecsUpdateListener: Send + Sync {
158    fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;
159
160    fn get_current_specs_info(&self) -> SpecsInfo;
161}
162
163impl fmt::Debug for dyn SpecsAdapter {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        write!(f, "{}", self.get_type_name())
166    }
167}