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 struct SpecsUpdate {
105 pub data: ResponseData,
106 pub source: SpecsSource,
107 pub received_at: u64,
108 pub source_api: Option<String>,
109}
110
111#[repr(C)]
112#[derive(Serialize, Deserialize, Debug, Clone)]
113pub struct SpecsInfo {
114 pub lcut: Option<u64>,
115 pub checksum: 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 source: SpecsSource::NoValues,
127 source_api: None,
128 }
129 }
130
131 #[must_use]
132 pub fn error() -> Self {
133 Self {
134 lcut: None,
135 checksum: None,
136 source: SpecsSource::Error,
137 source_api: None,
138 }
139 }
140}
141
142pub trait SpecsUpdateListener: Send + Sync {
143 fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;
144
145 fn get_current_specs_info(&self) -> SpecsInfo;
146}
147
148impl fmt::Debug for dyn SpecsAdapter {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(f, "{}", self.get_type_name())
151 }
152}