statsig_rust/evaluation/
dynamic_returnable.rs

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