statsig_rust/specs_adapter/
specs_adapter_trait.rs1use 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}
124
125#[repr(C)]
126#[derive(Serialize, Deserialize, Debug, Clone)]
127pub struct SpecsInfo {
128 pub lcut: Option<u64>,
129 pub checksum: Option<String>,
130 pub source: SpecsSource,
131 pub source_api: Option<String>,
132}
133
134impl SpecsInfo {
135 #[must_use]
136 pub fn empty() -> Self {
137 Self {
138 lcut: None,
139 checksum: None,
140 source: SpecsSource::NoValues,
141 source_api: None,
142 }
143 }
144
145 #[must_use]
146 pub fn error() -> Self {
147 Self {
148 lcut: None,
149 checksum: None,
150 source: SpecsSource::Error,
151 source_api: None,
152 }
153 }
154}
155
156pub trait SpecsUpdateListener: Send + Sync {
157 fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;
158
159 fn get_current_specs_info(&self) -> SpecsInfo;
160}
161
162impl fmt::Debug for dyn SpecsAdapter {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "{}", self.get_type_name())
165 }
166}