1use std::fmt::{Display, Formatter};
2
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7use crate::error::{GrowthbookError, GrowthbookErrorCode};
8use crate::extensions::JsonHelper;
9
10#[derive(Clone, PartialEq, Debug)]
11pub struct GrowthBookAttribute {
12 pub key: String,
13 pub value: GrowthBookAttributeValue,
14}
15
16#[derive(Clone, PartialEq, Debug)]
17pub enum GrowthBookAttributeValue {
18 Empty,
19 String(String),
20 Int(i64),
21 Float(f64),
22 Bool(bool),
23 Array(Vec<GrowthBookAttributeValue>),
24 Object(Vec<GrowthBookAttribute>),
25}
26
27#[derive(Serialize, Clone, Debug)]
28#[serde(rename_all = "camelCase")]
29pub struct FeatureResult {
30 pub value: Value,
31 pub on: bool,
32 pub off: bool,
33 pub experiment: Option<Experiment>,
34 pub experiment_result: Option<ExperimentResult>,
35 pub source: String,
36}
37
38#[derive(Serialize, Clone, Debug)]
39#[serde(rename_all = "camelCase")]
40pub struct Experiment {
41 pub name: Option<String>,
42 pub seed: Option<String>,
43 pub hash_version: Option<i64>,
44 pub hash_attribute: Option<String>,
45 pub namespace: Option<Vec<Value>>,
46 pub coverage: Option<f32>,
47 pub ranges: Option<Vec<Vec<f32>>>,
48 pub meta: Option<Value>,
49 pub filters: Option<Value>,
50 pub variations: Vec<Value>,
51 pub weights: Option<Vec<f32>>,
52 pub condition: Option<Value>,
53}
54
55#[derive(Serialize, Clone, Debug)]
56#[serde(rename_all = "camelCase")]
57pub struct ExperimentResult {
58 pub feature_id: String,
59 pub value: Value,
60 pub variation_id: i64,
61 pub in_experiment: bool,
62 pub hash_used: bool,
63 pub hash_attribute: Option<String>,
64 pub hash_value: Option<Value>,
65 pub bucket: Option<f32>,
66 pub key: String,
67 pub sticky_bucket_used: bool,
68}
69
70impl GrowthBookAttribute {
71 pub fn new(
72 key: String,
73 value: GrowthBookAttributeValue,
74 ) -> Self {
75 GrowthBookAttribute { key, value }
76 }
77
78 pub fn from(value: Value) -> Result<Vec<Self>, GrowthbookError> {
79 if !value.is_object() {
80 return Err(GrowthbookError::new(
81 GrowthbookErrorCode::GrowthBookAttributeIsNotObject,
82 "GrowthBookAttribute must be an object with at leat one key value pair",
83 ));
84 }
85
86 let default_map = Map::new();
87 let map = value.as_object().unwrap_or(&default_map);
88 let mut attributes = Vec::new();
89 for (key, value) in map {
90 attributes.push(GrowthBookAttribute {
91 key: key.clone(),
92 value: GrowthBookAttributeValue::from(value.clone()),
93 });
94 }
95 Ok(attributes)
96 }
97}
98
99impl GrowthBookAttributeValue {
100 pub fn is_number(&self) -> bool {
101 if let Ok(regex) = Regex::new("\\d+") {
102 regex.is_match(&self.to_string().replace('.', ""))
103 } else {
104 false
105 }
106 }
107 pub fn as_f64(&self) -> Option<f64> {
108 self.to_string().replace('.', "").parse::<f64>().ok()
109 }
110
111 pub fn to_value(&self) -> Value {
112 match self {
113 GrowthBookAttributeValue::Empty => Value::Null,
114 GrowthBookAttributeValue::String(it) => Value::from(it.clone()),
115 GrowthBookAttributeValue::Int(it) => Value::from(*it),
116 GrowthBookAttributeValue::Float(it) => Value::from(*it),
117 GrowthBookAttributeValue::Bool(it) => Value::from(*it),
118 GrowthBookAttributeValue::Array(it) => Value::Array(it.iter().map(|item| item.to_value()).collect()),
119 GrowthBookAttributeValue::Object(it) => {
120 let mut map = Map::new();
121 for attr in it {
122 map.insert(attr.key.clone(), attr.value.to_value());
123 }
124 Value::Object(map)
125 },
126 }
127 }
128}
129
130impl From<Value> for GrowthBookAttributeValue {
131 fn from(value: Value) -> Self {
132 if value.is_string() {
133 GrowthBookAttributeValue::String(value.as_str().unwrap_or_default().to_string())
134 } else if value.is_boolean() {
135 GrowthBookAttributeValue::Bool(value.as_bool().unwrap_or_default())
136 } else if value.is_i64() {
137 GrowthBookAttributeValue::Int(value.as_i64().unwrap_or_default())
138 } else if value.is_f64() {
139 GrowthBookAttributeValue::Float(value.as_f64().unwrap_or_default())
140 } else if value.is_array() {
141 let vec: Vec<GrowthBookAttributeValue> = value.as_array().unwrap_or(&vec![]).iter().map(|item| GrowthBookAttributeValue::from(item.clone())).collect();
142 GrowthBookAttributeValue::Array(vec)
143 } else {
144 let objects: Vec<_> = value
145 .as_object()
146 .unwrap_or(&Map::new())
147 .iter()
148 .map(|(k, v)| GrowthBookAttribute::new(k.clone(), GrowthBookAttributeValue::from(v.clone())))
149 .collect();
150
151 if objects.is_empty() {
152 GrowthBookAttributeValue::Empty
153 } else {
154 GrowthBookAttributeValue::Object(objects)
155 }
156 }
157 }
158}
159
160impl Display for GrowthBookAttributeValue {
161 fn fmt(
162 &self,
163 f: &mut Formatter<'_>,
164 ) -> std::fmt::Result {
165 let message = match self {
166 GrowthBookAttributeValue::Empty => String::new(),
167 GrowthBookAttributeValue::Array(it) => it.iter().fold(String::new(), |acc, value| format!("{acc}{}", value)),
168 GrowthBookAttributeValue::Object(it) => it.iter().fold(String::new(), |acc, att| format!("{acc}{}", att.value)),
169 GrowthBookAttributeValue::String(it) => it.clone(),
170 GrowthBookAttributeValue::Int(it) => it.to_string(),
171 GrowthBookAttributeValue::Float(it) => it.to_string(),
172 GrowthBookAttributeValue::Bool(it) => it.to_string(),
173 };
174
175 write!(f, "{}", message)
176 }
177}
178
179impl FeatureResult {
180 pub fn value_as<T>(&self) -> Result<T, GrowthbookError>
181 where
182 for<'a> T: Deserialize<'a>,
183 {
184 serde_json::from_value(self.value.clone()).map_err(GrowthbookError::from)
185 }
186
187 pub fn new(
188 value: Value,
189 on: bool,
190 source: String,
191 ) -> Self {
192 FeatureResult {
193 value,
194 on,
195 off: !on,
196 experiment: None,
197 experiment_result: None,
198 source,
199 }
200 }
201
202 pub fn force(value: Value) -> Self {
203 let is_on = is_on(&value);
204 FeatureResult {
205 value,
206 on: is_on,
207 off: !is_on,
208 experiment: None,
209 experiment_result: None,
210 source: String::from("force"),
211 }
212 }
213
214 pub fn experiment(
215 value: Value,
216 experiment: Experiment,
217 experiment_result: ExperimentResult,
218 ) -> Self {
219 let is_on = is_on(&value);
220 FeatureResult {
221 value,
222 on: is_on,
223 off: !is_on,
224 experiment: Some(experiment),
225 experiment_result: Some(experiment_result),
226 source: String::from("experiment"),
227 }
228 }
229
230 pub fn from_default_value(option_value: Option<Value>) -> Self {
231 let value = option_value.unwrap_or(Value::Null);
232 let is_on = is_on(&value);
233 Self {
234 value,
235 on: is_on,
236 off: !is_on,
237 experiment: None,
238 experiment_result: None,
239 source: String::from("defaultValue"),
240 }
241 }
242
243 pub fn prerequisite() -> Self {
244 Self {
245 value: Value::Null,
246 on: false,
247 off: true,
248 experiment: None,
249 experiment_result: None,
250 source: String::from("prerequisite"),
251 }
252 }
253
254 pub fn cyclic_prerequisite() -> Self {
255 Self {
256 value: Value::Null,
257 on: false,
258 off: true,
259 experiment: None,
260 experiment_result: None,
261 source: String::from("cyclicPrerequisite"),
262 }
263 }
264
265 pub fn unknown_feature() -> Self {
266 Self {
267 value: Value::Null,
268 on: false,
269 off: true,
270 experiment: None,
271 experiment_result: None,
272 source: String::from("unknownFeature"),
273 }
274 }
275}
276
277fn is_on(value: &Value) -> bool {
278 let is_on = if value.is_null() {
279 false
280 } else if (value.is_number() && value.force_f64(-1.0) != 0.0) || (value.is_string() && value.force_string("any") != "") {
281 true
282 } else if value.is_boolean() {
283 value.as_bool().unwrap_or(false)
284 } else if value.is_object() {
285 value.as_object().map(|it| !it.is_empty()).unwrap_or(false)
286 } else if value.is_array() {
287 value.as_array().map(|it| !it.is_empty()).unwrap_or(false)
288 } else {
289 false
290 };
291 is_on
292}