statsig_rust/specs_adapter/
specs_adapter_trait.rs1use 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}