Skip to main content

statsig_rust/evaluation/
dynamic_returnable.rs

1use std::{borrow::Cow, collections::HashMap, num::NonZeroU64, sync::Arc};
2
3use rkyv::{collections::swiss_table::ArchivedHashMap, string::ArchivedString};
4use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
5use serde_json::{
6    value::{to_raw_value, RawValue},
7    Value as JsonValue,
8};
9
10use crate::{
11    evaluation::rkyv_value::{stable_object_hash, ArchivedRkyvValue, RkyvValue},
12    hashing,
13    interned_values::InternedStore,
14    log_e,
15};
16
17const TAG: &str = "DynamicReturnable";
18
19lazy_static::lazy_static! {
20    static ref EMPTY_DYNAMIC_RETURNABLE: DynamicReturnable = DynamicReturnable {
21        hash: 0,
22        value: DynamicReturnableValue::Null,
23        stable_hash: NonZeroU64::new(hashing::hash_u64_slice(&[0])),
24    };
25
26    static ref TRUE_DYNAMIC_RETURNABLE: DynamicReturnable = DynamicReturnable {
27        hash: 0,
28        value: DynamicReturnableValue::Bool(true),
29        stable_hash: NonZeroU64::new(hashing::hash_u64_slice(&[1, 1])),
30    };
31
32    static ref FALSE_DYNAMIC_RETURNABLE: DynamicReturnable = DynamicReturnable {
33        hash: 0,
34        value: DynamicReturnableValue::Bool(false),
35        stable_hash: NonZeroU64::new(hashing::hash_u64_slice(&[1, 0])),
36    };
37
38}
39
40#[derive(Clone, Debug)]
41pub struct DynamicReturnable {
42    pub hash: u64,
43    pub value: DynamicReturnableValue,
44    stable_hash: Option<NonZeroU64>,
45}
46
47impl DynamicReturnable {
48    pub fn empty() -> Self {
49        EMPTY_DYNAMIC_RETURNABLE.clone()
50    }
51
52    pub fn from_bool(value: bool) -> Self {
53        if value {
54            TRUE_DYNAMIC_RETURNABLE.clone()
55        } else {
56            FALSE_DYNAMIC_RETURNABLE.clone()
57        }
58    }
59
60    pub fn from_map(value: HashMap<String, JsonValue>) -> Self {
61        let raw_value = match to_raw_value(&value) {
62            Ok(raw_value) => raw_value,
63            Err(e) => {
64                log_e!(TAG, "Failed to convert map to raw value: {}", e);
65                return Self::empty();
66            }
67        };
68
69        InternedStore::get_or_intern_returnable(Cow::Owned(raw_value))
70    }
71
72    pub fn get_bool(&self) -> Option<bool> {
73        match self.value {
74            DynamicReturnableValue::Bool(value) => Some(value),
75            _ => None,
76        }
77    }
78
79    pub fn get_json_archived_ref(
80        &self,
81    ) -> Option<&'static ArchivedHashMap<ArchivedString, ArchivedRkyvValue>> {
82        match self.value {
83            DynamicReturnableValue::JsonArchived(v) => Some(v),
84            _ => None,
85        }
86    }
87
88    pub fn get_json_pointer_ref(&self) -> Option<&HashMap<String, RkyvValue>> {
89        match &self.value {
90            DynamicReturnableValue::JsonPointer(v) => Some(v.as_ref()),
91            DynamicReturnableValue::JsonStatic(v) => Some(v),
92            _ => None,
93        }
94    }
95
96    pub fn get_json(&self) -> Option<HashMap<String, JsonValue>> {
97        match &self.value {
98            DynamicReturnableValue::JsonPointer(v) => rkyv_hashmap_to_owned_json(v.as_ref()),
99            DynamicReturnableValue::JsonStatic(v) => rkyv_hashmap_to_owned_json(v),
100            DynamicReturnableValue::JsonArchived(v) => archived_hashmap_to_owned(v).map_or_else(
101                |e| {
102                    log_e!(TAG, "Failed to convert archived json. Error: {}", e);
103                    None
104                },
105                Some,
106            ),
107            _ => None,
108        }
109    }
110
111    pub fn get_hash(&self) -> u64 {
112        self.hash
113    }
114
115    pub(crate) fn get_stable_hash(&self) -> u64 {
116        self.stable_hash
117            .map_or_else(|| stable_hash(&self.value), NonZeroU64::get)
118    }
119
120    pub(crate) fn from_interned_value(hash: u64, value: DynamicReturnableValue) -> Self {
121        Self {
122            hash,
123            stable_hash: NonZeroU64::new(stable_hash(&value)),
124            value,
125        }
126    }
127
128    pub(crate) fn from_archived_value(
129        hash: u64,
130        value: &'static ArchivedHashMap<ArchivedString, ArchivedRkyvValue>,
131    ) -> Self {
132        Self {
133            hash,
134            value: DynamicReturnableValue::JsonArchived(value),
135            stable_hash: None,
136        }
137    }
138
139    #[cfg(test)]
140    pub(crate) fn has_precomputed_stable_hash(&self) -> bool {
141        self.stable_hash.is_some()
142    }
143}
144
145impl PartialEq for DynamicReturnable {
146    fn eq(&self, other: &Self) -> bool {
147        self.hash == other.hash && self.value == other.value
148    }
149}
150
151fn stable_hash(value: &DynamicReturnableValue) -> u64 {
152    match value {
153        DynamicReturnableValue::Null => hashing::hash_u64_slice(&[0]),
154        DynamicReturnableValue::Bool(value) => hashing::hash_u64_slice(&[1, u64::from(*value)]),
155        DynamicReturnableValue::JsonPointer(value) => {
156            stable_object_hash(value.iter().map(|(key, value)| (key.as_str(), value)))
157        }
158        DynamicReturnableValue::JsonStatic(value) => {
159            stable_object_hash(value.iter().map(|(key, value)| (key.as_str(), value)))
160        }
161        DynamicReturnableValue::JsonArchived(value) => {
162            stable_object_hash(value.iter().map(|(key, value)| (key.as_str(), value)))
163        }
164    }
165}
166
167impl<'de> Deserialize<'de> for DynamicReturnable {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        let raw_value_ref: Cow<RawValue> = Deserialize::deserialize(deserializer)?;
173        Ok(InternedStore::get_or_intern_returnable(raw_value_ref))
174    }
175}
176
177impl Serialize for DynamicReturnable {
178    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179    where
180        S: Serializer,
181    {
182        match &self.value {
183            DynamicReturnableValue::JsonPointer(raw) => raw.serialize(serializer),
184            DynamicReturnableValue::JsonStatic(raw) => raw.serialize(serializer),
185            DynamicReturnableValue::JsonArchived(raw) => {
186                let mut map = serializer.serialize_map(Some(raw.len()))?;
187                for (key, value) in raw.iter() {
188                    map.serialize_entry(key.as_str(), value)?;
189                }
190                map.end()
191            }
192            DynamicReturnableValue::Null => serializer.serialize_none(),
193            DynamicReturnableValue::Bool(value) => serializer.serialize_bool(*value),
194        }
195    }
196}
197
198// ------------------------------------------------------------------------------- [ DynamicReturnableValue ]
199
200#[derive(Clone, Debug)]
201pub enum DynamicReturnableValue {
202    Null,
203    Bool(bool),
204    JsonPointer(Arc<HashMap<String, RkyvValue>>),
205    JsonStatic(&'static HashMap<String, RkyvValue>),
206    JsonArchived(&'static ArchivedHashMap<ArchivedString, ArchivedRkyvValue>),
207}
208
209impl PartialEq for DynamicReturnableValue {
210    fn eq(&self, other: &Self) -> bool {
211        match (self, other) {
212            (DynamicReturnableValue::Null, DynamicReturnableValue::Null) => return true,
213            (DynamicReturnableValue::Bool(a), DynamicReturnableValue::Bool(b)) => return *a == *b,
214            _ => {}
215        };
216
217        if let DynamicReturnableValue::JsonPointer(a) = self {
218            match other {
219                DynamicReturnableValue::JsonPointer(b) => return a.as_ref() == b.as_ref(),
220                DynamicReturnableValue::JsonStatic(b) => return a.as_ref() == *b,
221                DynamicReturnableValue::JsonArchived(b) => return eq_check(b, a.as_ref()),
222                _ => return false,
223            }
224        }
225
226        if let DynamicReturnableValue::JsonStatic(a) = self {
227            match other {
228                DynamicReturnableValue::JsonPointer(b) => return *a == b.as_ref(),
229                DynamicReturnableValue::JsonStatic(b) => return a == b,
230                DynamicReturnableValue::JsonArchived(b) => return eq_check(b, a),
231                _ => return false,
232            }
233        }
234
235        if let DynamicReturnableValue::JsonArchived(a) = self {
236            match other {
237                DynamicReturnableValue::JsonPointer(b) => return eq_check(a, b.as_ref()),
238                DynamicReturnableValue::JsonStatic(b) => return eq_check(a, b),
239                DynamicReturnableValue::JsonArchived(b) => return a == b,
240                _ => return false,
241            }
242        }
243
244        false
245    }
246}
247
248// ------------------------------------------------------------------------------- [ Rkyv Helper ]
249
250fn eq_check(
251    left: &ArchivedHashMap<ArchivedString, ArchivedRkyvValue>,
252    right: &HashMap<String, RkyvValue>,
253) -> bool {
254    for (key, value) in left.iter() {
255        match right.get_key_value(key.as_str()) {
256            Some((left_key, left_value)) => {
257                if left_key != key {
258                    return false;
259                }
260                if left_value != value {
261                    return false;
262                }
263            }
264            None => return false,
265        };
266    }
267    true
268}
269
270fn rkyv_hashmap_to_owned_json(
271    raw: &HashMap<String, RkyvValue>,
272) -> Option<HashMap<String, JsonValue>> {
273    match serde_json::to_value(raw) {
274        Ok(JsonValue::Object(o)) => Some(o.into_iter().collect()),
275        Ok(_) => None,
276        Err(e) => {
277            log_e!(TAG, "Failed to convert json. Error: {}", e);
278            None
279        }
280    }
281}
282
283fn archived_hashmap_to_owned(
284    raw: &'static ArchivedHashMap<ArchivedString, ArchivedRkyvValue>,
285) -> Result<HashMap<String, JsonValue>, serde_json::Error> {
286    let mut taken: HashMap<String, JsonValue> = HashMap::new();
287    for (key, value) in raw.iter() {
288        taken.insert(key.as_str().to_string(), serde_json::to_value(value)?);
289    }
290
291    Ok(taken)
292}