statsig_rust/evaluation/
dynamic_returnable.rs

1use crate::{hashing::djb2, log_e};
2use parking_lot::Mutex;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use serde_json::{
5    value::{to_raw_value, RawValue},
6    Value as JsonValue,
7};
8use std::{
9    collections::HashMap,
10    sync::{Arc, Weak},
11    time::Duration,
12};
13
14const TAG: &str = "DynamicReturnable";
15
16lazy_static::lazy_static! {
17    pub(crate) static ref MEMOIZED_VALUES: Mutex<HashMap<String, Weak<MemoizedValue>>> =
18        Mutex::new(HashMap::new());
19
20    static ref EMPTY_DYNAMIC_RETURNABLE: DynamicReturnable = DynamicReturnable {
21        hash: "".to_string(),
22        value: Arc::new(MemoizedValue {
23            raw_value: None,
24            bool_value: None,
25            json_value: None,
26        }),
27    };
28}
29
30#[derive(Clone, PartialEq, Debug)]
31pub struct DynamicReturnable {
32    hash: String,
33    value: Arc<MemoizedValue>,
34}
35
36impl DynamicReturnable {
37    pub fn empty() -> Self {
38        EMPTY_DYNAMIC_RETURNABLE.clone()
39    }
40
41    pub fn from_map(value: HashMap<String, JsonValue>) -> Self {
42        let raw_value = match to_raw_value(&value) {
43            Ok(raw_value) => raw_value,
44            Err(e) => {
45                log_e!(TAG, "Failed to convert map to raw value: {}", e);
46                return Self::empty();
47            }
48        };
49
50        let hash = djb2(raw_value.get());
51        let value = Arc::new(MemoizedValue {
52            raw_value: Some(raw_value),
53            bool_value: None,
54            json_value: Some(value.clone()),
55        });
56
57        Self::new(hash.to_string(), value)
58    }
59
60    pub fn get_bool(&self) -> Option<bool> {
61        self.value.bool_value
62    }
63
64    pub fn get_json(&self) -> Option<HashMap<String, JsonValue>> {
65        self.value.json_value.clone()
66    }
67
68    pub fn get_json_ref(&self) -> Option<&HashMap<String, JsonValue>> {
69        self.value.json_value.as_ref()
70    }
71
72    fn new(hash: String, value: Arc<MemoizedValue>) -> Self {
73        let weak_value = Arc::downgrade(&value);
74        set_memoized_value(&hash, weak_value);
75
76        Self { hash, value }
77    }
78}
79
80impl<'de> Deserialize<'de> for DynamicReturnable {
81    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82    where
83        D: Deserializer<'de>,
84    {
85        let raw_value_ref: &'de RawValue = Deserialize::deserialize(deserializer)?;
86
87        let raw_value_str = raw_value_ref.get();
88        let hash = djb2(raw_value_str);
89
90        if let Some(value) = get_memoized_value(&hash) {
91            return Ok(DynamicReturnable { hash, value });
92        }
93
94        let raw_value = raw_value_ref.to_owned();
95        let value = MemoizedValue::new(raw_value);
96
97        let new_returnable = DynamicReturnable::new(hash.clone(), value);
98
99        Ok(new_returnable)
100    }
101}
102
103impl Serialize for DynamicReturnable {
104    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
105    where
106        S: Serializer,
107    {
108        if let Some(bool_value) = self.value.bool_value {
109            return bool_value.serialize(serializer);
110        }
111
112        if let Some(raw_value) = &self.value.raw_value {
113            return raw_value.serialize(serializer);
114        }
115
116        if let Some(json_value) = &self.value.json_value {
117            return json_value.serialize(serializer);
118        }
119
120        JsonValue::Null.serialize(serializer)
121    }
122}
123
124impl Drop for DynamicReturnable {
125    fn drop(&mut self) {
126        let mut memo = match MEMOIZED_VALUES.try_lock_for(Duration::from_secs(5)) {
127            Some(values) => values,
128            None => {
129                log_e!(
130                    TAG,
131                    "Failed to lock memoized values: Failed to lock MEMOIZED_VALUES"
132                );
133                return;
134            }
135        };
136
137        let found = match memo.get(&self.hash) {
138            Some(value) => value,
139            None => return,
140        };
141
142        if found.strong_count() == 1 {
143            memo.remove(&self.hash);
144        }
145    }
146}
147
148#[derive(Debug, Clone)]
149pub(crate) struct MemoizedValue {
150    pub(crate) raw_value: Option<Box<RawValue>>,
151    pub(crate) bool_value: Option<bool>,
152    pub(crate) json_value: Option<HashMap<String, JsonValue>>,
153}
154
155impl PartialEq for MemoizedValue {
156    fn eq(&self, other: &Self) -> bool {
157        self.raw_value.as_ref().map(|v| v.get()) == other.raw_value.as_ref().map(|v| v.get())
158            && self.bool_value == other.bool_value
159            && self.json_value == other.json_value
160    }
161}
162
163impl MemoizedValue {
164    fn new(raw_value: Box<RawValue>) -> Arc<Self> {
165        let value = match raw_value.get() {
166            "true" => Self::from_bool(true),
167            "false" => Self::from_bool(false),
168            _ => Self::from_raw_value(raw_value),
169        };
170
171        Arc::new(value)
172    }
173
174    fn from_bool(bool_value: bool) -> Self {
175        Self {
176            raw_value: None,
177            bool_value: Some(bool_value),
178            json_value: None,
179        }
180    }
181
182    fn from_raw_value(raw_value: Box<RawValue>) -> Self {
183        let json_value = match serde_json::from_str(raw_value.get()) {
184            Ok(json_value) => json_value,
185            Err(e) => {
186                log_e!(TAG, "Failed to parse json: {}", e);
187                None
188            }
189        };
190
191        Self {
192            raw_value: Some(raw_value),
193            bool_value: None,
194            json_value,
195        }
196    }
197}
198
199fn get_memoized_value(hash: &str) -> Option<Arc<MemoizedValue>> {
200    let mut memoized_values = match MEMOIZED_VALUES.try_lock_for(Duration::from_secs(5)) {
201        Some(values) => values,
202        None => {
203            log_e!(
204                TAG,
205                "Failed to lock memoized values: Failed to lock MEMOIZED_VALUES"
206            );
207            return None;
208        }
209    };
210
211    let found = memoized_values.get(hash)?;
212
213    match found.upgrade() {
214        Some(value) => Some(value),
215        None => {
216            memoized_values.remove(hash);
217            None
218        }
219    }
220}
221
222fn set_memoized_value(hash: &str, value: Weak<MemoizedValue>) {
223    match MEMOIZED_VALUES.try_lock_for(Duration::from_secs(5)) {
224        Some(mut values) => {
225            values.insert(hash.to_string(), value);
226        }
227        None => {
228            log_e!(
229                TAG,
230                "Failed to lock memoized values: Failed to lock MEMOIZED_VALUES"
231            );
232        }
233    };
234}