Skip to main content

statsig_rust/evaluation/
dynamic_string.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3
4use crate::{hashing::ahash_str, interned_string::InternedString};
5
6lazy_static::lazy_static! {
7    static ref USER_ID: DynamicString = DynamicString::from("userID".to_string());
8    static ref STABLE_ID: DynamicString = DynamicString::from("stableID".to_string());
9}
10
11#[derive(Clone, Eq, Debug)]
12pub struct DynamicString {
13    pub value: InternedString,
14    pub lowercased_value: InternedString,
15    pub hash_value: u64,
16}
17
18impl DynamicString {
19    pub fn user_id() -> Self {
20        USER_ID.clone()
21    }
22
23    pub fn stable_id() -> Self {
24        STABLE_ID.clone()
25    }
26
27    pub fn from_bool(value: bool) -> Self {
28        let raw = if value { "true" } else { "false" };
29        Self {
30            value: InternedString::from_bool(value),
31            lowercased_value: InternedString::from_bool(value),
32            hash_value: ahash_str(raw),
33        }
34    }
35}
36
37// ------------------------------------------------------------------------------- [Serialization]
38
39impl Serialize for DynamicString {
40    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41    where
42        S: Serializer,
43    {
44        if let Ok(bool_value) = self.value.parse::<bool>() {
45            return serializer.serialize_bool(bool_value);
46        }
47
48        serializer.serialize_str(&self.value)
49    }
50}
51
52impl<'de> Deserialize<'de> for DynamicString {
53    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54    where
55        D: Deserializer<'de>,
56    {
57        let value = String::deserialize(deserializer)?;
58        Ok(DynamicString::from(value))
59    }
60}
61
62// ------------------------------------------------------------------------------- [PartialEq]
63
64impl PartialEq for DynamicString {
65    fn eq(&self, other: &Self) -> bool {
66        self.value == other.value
67    }
68}
69
70impl PartialEq<&str> for DynamicString {
71    fn eq(&self, other: &&str) -> bool {
72        self.value.as_str() == *other
73    }
74}
75
76impl PartialEq<String> for DynamicString {
77    fn eq(&self, other: &String) -> bool {
78        self.value == *other
79    }
80}
81
82impl PartialEq<&String> for DynamicString {
83    fn eq(&self, other: &&String) -> bool {
84        self.value.as_str() == other.as_str()
85    }
86}
87
88// ------------------------------------------------------------------------------- [From<T> Implementations]
89
90impl From<Value> for DynamicString {
91    fn from(value: Value) -> Self {
92        let str_value = match value.as_str() {
93            Some(value) => value.to_string(),
94            None => value.to_string(),
95        };
96        DynamicString::from(str_value)
97    }
98}
99
100impl From<String> for DynamicString {
101    fn from(value: String) -> Self {
102        let hash_value = ahash_str(&value);
103        let value = InternedString::from_string(value);
104        let lowercased_value = if value.as_str().bytes().any(|byte| byte.is_ascii_uppercase()) {
105            InternedString::from_string(value.as_str().to_lowercase())
106        } else {
107            value.clone()
108        };
109
110        Self {
111            value,
112            lowercased_value,
113            hash_value,
114        }
115    }
116}