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 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}