statsig_rust/evaluation/
dynamic_returnable.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, Mutex, Weak},
4};
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use serde_json::{value::RawValue, Value as JsonValue};
8
9use crate::{hashing::djb2, log_e};
10
11const TAG: &str = "DynamicReturnable";
12
13lazy_static::lazy_static! {
14    static ref MEMOIZED_VALUES: Mutex<HashMap<String, Weak<MemoizedValue>>> =
15        Mutex::new(HashMap::new());
16}
17
18#[derive(Clone, PartialEq, Debug)]
19pub struct DynamicReturnable {
20    hash: String,
21    value: Arc<MemoizedValue>,
22}
23
24impl DynamicReturnable {
25    pub fn get_bool(&self) -> Option<bool> {
26        self.value.bool_value
27    }
28
29    pub fn get_json(&self) -> Option<HashMap<String, JsonValue>> {
30        self.value.json_value.clone()
31    }
32
33    fn new(hash: String, value: Arc<MemoizedValue>) -> Self {
34        Self { hash, value }
35    }
36}
37
38impl<'de> Deserialize<'de> for DynamicReturnable {
39    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40    where
41        D: Deserializer<'de>,
42    {
43        let raw_value: &'de RawValue = Deserialize::deserialize(deserializer)?;
44        let raw_value_str = raw_value.get();
45        let hash = djb2(raw_value_str);
46
47        if let Some(value) = get_memoized_value(&hash) {
48            return Ok(DynamicReturnable { hash, value });
49        }
50
51        let value = MemoizedValue::new(raw_value_str);
52        let weak_value = Arc::downgrade(&value);
53
54        let new_returnable = DynamicReturnable::new(hash.clone(), value);
55        set_memoized_value(&hash, weak_value);
56
57        Ok(new_returnable)
58    }
59}
60
61impl Serialize for DynamicReturnable {
62    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63    where
64        S: Serializer,
65    {
66        if let Some(bool_value) = self.value.bool_value {
67            return bool_value.serialize(serializer);
68        }
69
70        if let Some(json_value) = &self.value.json_value {
71            return json_value.serialize(serializer);
72        }
73
74        JsonValue::Null.serialize(serializer)
75    }
76}
77
78impl Drop for DynamicReturnable {
79    fn drop(&mut self) {
80        let mut memo = match MEMOIZED_VALUES.lock() {
81            Ok(values) => values,
82            Err(e) => {
83                log_e!(TAG, "Failed to lock memoized values: {}", e);
84                return;
85            }
86        };
87
88        let found = match memo.get(&self.hash) {
89            Some(value) => value,
90            None => return,
91        };
92
93        if found.strong_count() == 1 {
94            memo.remove(&self.hash);
95        }
96    }
97}
98
99#[derive(Debug, Clone, PartialEq)]
100struct MemoizedValue {
101    bool_value: Option<bool>,
102    json_value: Option<HashMap<String, JsonValue>>,
103}
104
105impl MemoizedValue {
106    fn new(raw_json: &str) -> Arc<Self> {
107        let value = match raw_json {
108            "true" | "false" => Self::from_bool(raw_json == "true"),
109            raw_json => Self::from_object_str(raw_json),
110        };
111
112        Arc::new(value)
113    }
114
115    fn from_bool(bool_value: bool) -> Self {
116        Self {
117            bool_value: Some(bool_value),
118            json_value: None,
119        }
120    }
121
122    fn from_object_str(raw_json: &str) -> Self {
123        let json_value = match serde_json::from_str(raw_json) {
124            Ok(json_value) => json_value,
125            Err(e) => {
126                log_e!(TAG, "Failed to parse json: {}", e);
127                None
128            }
129        };
130
131        Self {
132            bool_value: None,
133            json_value,
134        }
135    }
136}
137
138fn get_memoized_value(hash: &str) -> Option<Arc<MemoizedValue>> {
139    let mut memoized_values = match MEMOIZED_VALUES.lock() {
140        Ok(values) => values,
141        Err(e) => {
142            log_e!(TAG, "Failed to lock memoized values: {}", e);
143            return None;
144        }
145    };
146
147    let found = memoized_values.get(hash)?;
148
149    match found.upgrade() {
150        Some(value) => Some(value),
151        None => {
152            memoized_values.remove(hash);
153            None
154        }
155    }
156}
157
158fn set_memoized_value(hash: &str, value: Weak<MemoizedValue>) {
159    match MEMOIZED_VALUES.lock() {
160        Ok(mut values) => {
161            values.insert(hash.to_string(), value);
162        }
163        Err(e) => {
164            log_e!(TAG, "Failed to lock memoized values: {}", e);
165        }
166    };
167}