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