growthbook_sdk_rust/
model_public.rs

1use std::fmt::{Display, Formatter};
2
3use regex::Regex;
4use serde_json::{Map, Value};
5
6use crate::error::{GrowthbookError, GrowthbookErrorCode};
7
8#[derive(Clone, PartialEq, Debug)]
9pub struct GrowthBookAttribute {
10    pub key: String,
11    pub value: GrowthBookAttributeValue,
12}
13
14#[derive(Clone, PartialEq, Debug)]
15pub enum GrowthBookAttributeValue {
16    Empty,
17    String(String),
18    Int(i64),
19    Float(f64),
20    Bool(bool),
21    Array(Vec<GrowthBookAttributeValue>),
22    Object(Vec<GrowthBookAttribute>),
23}
24
25impl GrowthBookAttribute {
26    pub fn new(
27        key: String,
28        value: GrowthBookAttributeValue,
29    ) -> Self {
30        GrowthBookAttribute { key, value }
31    }
32
33    pub fn from(value: Value) -> Result<Vec<Self>, GrowthbookError> {
34        if !value.is_object() {
35            return Err(GrowthbookError::new(
36                GrowthbookErrorCode::GrowthBookAttributeIsNotObject,
37                "GrowthBookAttribute must be an object with at leat one key value pair",
38            ));
39        }
40
41        let default_map = Map::new();
42        let map = value.as_object().unwrap_or(&default_map);
43        let mut attributes = Vec::new();
44        for (key, value) in map {
45            attributes.push(GrowthBookAttribute {
46                key: key.clone(),
47                value: GrowthBookAttributeValue::from(value.clone()),
48            });
49        }
50        Ok(attributes)
51    }
52}
53
54impl GrowthBookAttributeValue {
55    pub fn is_number(&self) -> bool {
56        if let Ok(regex) = Regex::new("\\d+") {
57            regex.is_match(&self.to_string().replace('.', ""))
58        } else {
59            false
60        }
61    }
62    pub fn as_f64(&self) -> Option<f64> {
63        self.to_string().replace('.', "").parse::<f64>().ok()
64    }
65
66    pub fn to_value(&self) -> Value {
67        match self {
68            GrowthBookAttributeValue::Empty => Value::Null,
69            GrowthBookAttributeValue::String(it) => Value::from(it.clone()),
70            GrowthBookAttributeValue::Int(it) => Value::from(*it),
71            GrowthBookAttributeValue::Float(it) => Value::from(*it),
72            GrowthBookAttributeValue::Bool(it) => Value::from(*it),
73            GrowthBookAttributeValue::Array(it) => Value::Array(it.iter().map(|item| item.to_value()).collect()),
74            GrowthBookAttributeValue::Object(it) => {
75                let mut map = Map::new();
76                for attr in it {
77                    map.insert(attr.key.clone(), attr.value.to_value());
78                }
79                Value::Object(map)
80            },
81        }
82    }
83}
84
85impl From<Value> for GrowthBookAttributeValue {
86    fn from(value: Value) -> Self {
87        if value.is_string() {
88            GrowthBookAttributeValue::String(value.as_str().unwrap_or_default().to_string())
89        } else if value.is_boolean() {
90            GrowthBookAttributeValue::Bool(value.as_bool().unwrap_or_default())
91        } else if value.is_i64() {
92            GrowthBookAttributeValue::Int(value.as_i64().unwrap_or_default())
93        } else if value.is_f64() {
94            GrowthBookAttributeValue::Float(value.as_f64().unwrap_or_default())
95        } else if value.is_array() {
96            let vec: Vec<GrowthBookAttributeValue> = value.as_array().unwrap_or(&vec![]).iter().map(|item| GrowthBookAttributeValue::from(item.clone())).collect();
97            GrowthBookAttributeValue::Array(vec)
98        } else {
99            let objects: Vec<_> = value
100                .as_object()
101                .unwrap_or(&Map::new())
102                .iter()
103                .map(|(k, v)| GrowthBookAttribute::new(k.clone(), GrowthBookAttributeValue::from(v.clone())))
104                .collect();
105
106            if objects.is_empty() {
107                GrowthBookAttributeValue::Empty
108            } else {
109                GrowthBookAttributeValue::Object(objects)
110            }
111        }
112    }
113}
114
115impl Display for GrowthBookAttributeValue {
116    fn fmt(
117        &self,
118        f: &mut Formatter<'_>,
119    ) -> std::fmt::Result {
120        let message = match self {
121            GrowthBookAttributeValue::Empty => String::new(),
122            GrowthBookAttributeValue::Array(it) => it.iter().fold(String::new(), |acc, value| format!("{acc}{}", value)),
123            GrowthBookAttributeValue::Object(it) => it.iter().fold(String::new(), |acc, att| format!("{acc}{}", att.value)),
124            GrowthBookAttributeValue::String(it) => it.clone(),
125            GrowthBookAttributeValue::Int(it) => it.to_string(),
126            GrowthBookAttributeValue::Float(it) => it.to_string(),
127            GrowthBookAttributeValue::Bool(it) => it.to_string(),
128        };
129
130        write!(f, "{}", message)
131    }
132}