statsig_rust/
data_store_interface.rs1use std::fmt::Display;
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5
6use crate::{hashing::HashUtil, StatsigErr, StatsigOptions};
7
8pub enum RequestPath {
9 RulesetsV2,
10 RulesetsV1,
11 IDListsV1,
12 IDList,
13}
14
15impl Display for RequestPath {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 let value = match self {
18 RequestPath::IDListsV1 => "/v1/get_id_lists",
19 RequestPath::IDList => "id_list",
20 RequestPath::RulesetsV2 => "/v2/download_config_specs",
21 RequestPath::RulesetsV1 => "/v1/download_config_specs",
22 };
23 write!(f, "{value}")
24 }
25}
26
27pub enum CompressFormat {
28 PlainText,
29 Gzip,
30 StatsigBr,
31}
32
33impl Display for CompressFormat {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 let value = match self {
36 CompressFormat::PlainText => "plain_text",
37 CompressFormat::Gzip => "gzip",
38 CompressFormat::StatsigBr => "statsig-br",
39 };
40 write!(f, "{value}")
41 }
42}
43
44#[derive(Deserialize, Serialize)]
45pub struct DataStoreResponse {
46 pub result: Option<String>,
47 pub time: Option<u64>,
48}
49
50#[derive(Deserialize, Serialize)]
51pub struct DataStoreBytesResponse {
52 pub result: Option<Vec<u8>>,
53 pub time: Option<u64>,
54}
55
56#[derive(Clone, Debug)]
57pub(crate) struct DataStoreCacheKeys {
58 pub plain_text: String,
59 pub statsig_br: String,
60}
61
62impl DataStoreCacheKeys {
63 pub(crate) fn from_selected_key(key: &str) -> Self {
64 Self {
65 plain_text: data_store_key_with_compress_format(key, CompressFormat::PlainText),
66 statsig_br: data_store_key_with_compress_format(key, CompressFormat::StatsigBr),
67 }
68 }
69}
70
71#[async_trait]
72pub trait DataStoreTrait: Send + Sync {
73 async fn initialize(&self) -> Result<(), StatsigErr>;
74 async fn shutdown(&self) -> Result<(), StatsigErr>;
75 async fn get(&self, key: &str) -> Result<DataStoreResponse, StatsigErr>;
76 async fn set(&self, key: &str, value: &str, time: Option<u64>) -> Result<(), StatsigErr>;
77 async fn set_bytes(
78 &self,
79 key: &str,
80 value: &[u8],
81 time: Option<u64>,
82 ) -> Result<(), StatsigErr> {
83 let _ = (key, value, time);
84 Err(StatsigErr::BytesNotImplemented)
85 }
86
87 async fn get_bytes(&self, key: &str) -> Result<DataStoreBytesResponse, StatsigErr> {
88 let _ = key;
89 Err(StatsigErr::BytesNotImplemented)
90 }
91
92 async fn support_polling_updates_for(&self, path: RequestPath) -> bool;
93}
94
95#[derive(Clone, Debug, Default)]
96pub enum DataStoreKeyVersion {
97 #[default]
98 V2Hashed,
99 V3HumanReadable,
100}
101
102impl From<&str> for DataStoreKeyVersion {
103 fn from(level: &str) -> Self {
104 match level.to_lowercase().as_str() {
105 "v2" | "2" => DataStoreKeyVersion::V2Hashed,
106 "v3" | "3" => DataStoreKeyVersion::V3HumanReadable,
107 _ => DataStoreKeyVersion::default(),
108 }
109 }
110}
111
112#[must_use]
113pub(crate) fn get_data_store_key(
114 path: RequestPath,
115 sdk_key: &str,
116 hashing: &HashUtil,
117 options: &StatsigOptions,
118) -> String {
119 let key = match options
120 .data_store_key_schema_version
121 .clone()
122 .unwrap_or_default()
123 {
124 DataStoreKeyVersion::V3HumanReadable => {
125 let mut key = sdk_key.to_string();
126 key.truncate(20);
127 key
128 }
129 DataStoreKeyVersion::V2Hashed => hashing.hash(sdk_key, &crate::HashAlgorithm::Sha256),
130 };
131
132 format!("statsig|{path}|{}|{key}", CompressFormat::PlainText)
133}
134
135fn data_store_key_with_compress_format(key: &str, compress_format: CompressFormat) -> String {
136 let target = format!("|{compress_format}|");
137
138 [
139 CompressFormat::PlainText,
140 CompressFormat::Gzip,
141 CompressFormat::StatsigBr,
142 ]
143 .into_iter()
144 .map(|format| format!("|{format}|"))
145 .find_map(|current| {
146 key.contains(¤t)
147 .then(|| key.replacen(¤t, &target, 1))
148 })
149 .unwrap_or_else(|| key.to_string())
150}