Skip to main content

statsig_rust/
data_store_interface.rs

1use 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(&current)
147            .then(|| key.replacen(&current, &target, 1))
148    })
149    .unwrap_or_else(|| key.to_string())
150}