growthbook_sdk_rust/
model_public.rs1use 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}