statsig_rust/persistent_storage/
persistent_storage_trait.rs1use std::{collections::HashMap, sync::Weak};
2
3use chrono::Utc;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::evaluation::dynamic_string::DynamicString;
8use crate::{
9 evaluation::evaluation_types::{ExperimentEvaluation, LayerEvaluation},
10 event_logging::event_logger::EventLogger,
11 statsig_type_factories::{extract_from_experiment_evaluation, make_layer},
12 statsig_types::{Experiment, Layer},
13 unwrap_or_return,
14 user::StatsigUserInternal,
15 EvaluationDetails, SamplingProcessor, SecondaryExposure,
16};
17
18pub type UserPersistedValues = HashMap<String, StickyValues>;
19
20pub trait PersistentStorage: Send + Sync {
21 fn load(&self, key: String) -> Option<UserPersistedValues>;
22 fn save(&self, key: &str, config_name: &str, data: StickyValues);
23 fn delete(&self, key: &str, config_name: &str);
24}
25
26pub fn get_persistent_storage_key(user: &StatsigUserInternal, id_type: &String) -> Option<String> {
27 let dyn_str_id_type = DynamicString::from(id_type.clone());
28 user.get_unit_id(&dyn_str_id_type).map(|id| {
29 let mut id_str = "";
30 if let Some(id) = id.string_value.as_ref().map(|s| &s.value) {
31 id_str = id;
32 }
33
34 format!("{}:{}", id_str, id_type)
35 })
36}
37
38#[derive(Serialize, Deserialize, Debug, Clone, Default)]
39pub struct StickyValues {
40 pub value: bool,
41 pub json_value: Option<HashMap<String, Value>>,
42 pub rule_id: Option<String>,
43 pub group_name: Option<String>,
44 pub secondary_exposures: Vec<SecondaryExposure>,
45 pub undelegated_secondary_exposures: Option<Vec<SecondaryExposure>>,
46 pub config_delegate: Option<String>,
47 pub explicit_parameters: Option<Vec<String>>,
48 pub time: Option<u64>,
49 pub config_version: Option<u32>,
50}
51
52pub fn make_layer_from_sticky_value(
53 name: &str,
54 user: &StatsigUserInternal,
55 evaluation: LayerEvaluation,
56 sticky_value: StickyValues,
57 event_logger_ptr: Option<Weak<EventLogger>>,
58 sampling_processor: Option<Weak<SamplingProcessor>>,
59 disable_exposure: bool,
60) -> Layer {
61 let details = EvaluationDetails {
62 reason: "Persisted".to_owned(),
63 lcut: sticky_value.time,
64 received_at: Some(Utc::now().timestamp_millis() as u64),
65 };
66 make_layer(
67 user.to_loggable(),
68 name,
69 Some(evaluation),
70 details,
71 event_logger_ptr,
72 sticky_value.config_version,
73 disable_exposure,
74 sampling_processor,
75 None,
76 )
77}
78
79pub fn make_sticky_value_from_layer(layer: &Layer) -> Option<StickyValues> {
80 let layer_evaluation = unwrap_or_return!(layer.__evaluation.as_ref(), None);
81 Some(StickyValues {
82 value: true,
83 json_value: Some(layer_evaluation.value.clone()),
84 rule_id: Some(layer_evaluation.base.rule_id.clone()),
85 group_name: layer_evaluation.group_name.clone(),
86 secondary_exposures: layer_evaluation.base.secondary_exposures.clone(),
87 undelegated_secondary_exposures: layer_evaluation.undelegated_secondary_exposures.clone(),
88 config_delegate: layer_evaluation.allocated_experiment_name.clone(),
89 explicit_parameters: Some(layer_evaluation.explicit_parameters.clone()),
90 time: layer.details.lcut,
91 config_version: layer.__version,
92 })
93}
94
95pub fn make_experiment_from_sticky_value(
96 evaluation: ExperimentEvaluation,
97 sticky_value: StickyValues,
98) -> Experiment {
99 let name = evaluation.base.name.clone();
100 let maybe_evaluation = Some(evaluation);
101 let (value, rule_id, id_type, group_name) =
102 extract_from_experiment_evaluation(&maybe_evaluation);
103 let details = EvaluationDetails {
104 reason: "Persisted".to_owned(),
105 lcut: sticky_value.time,
106 received_at: Some(Utc::now().timestamp_millis() as u64),
107 };
108 Experiment {
109 name,
110 value,
111 rule_id,
112 id_type,
113 group_name,
114 details,
115 __evaluation: maybe_evaluation,
116 __version: sticky_value.config_version,
117 __override_config_name: None,
118 }
119}
120
121pub fn make_sticky_value_from_experiment(experiment: &Experiment) -> Option<StickyValues> {
122 let experiment_evaluation = unwrap_or_return!(&experiment.__evaluation, None);
123 Some(StickyValues {
124 value: true, json_value: Some(experiment_evaluation.value.clone()),
126 rule_id: Some(experiment_evaluation.base.rule_id.clone()),
127 group_name: experiment_evaluation.group_name.clone(),
128 secondary_exposures: experiment_evaluation.base.secondary_exposures.clone(),
129 undelegated_secondary_exposures: experiment_evaluation
130 .undelegated_secondary_exposures
131 .clone(),
132 config_delegate: None,
133 explicit_parameters: experiment_evaluation.explicit_parameters.clone(),
134 time: experiment.details.lcut,
135 config_version: experiment.__version,
136 })
137}