1use std::collections::HashMap;
2
3use serde::Deserialize;
4use serde_json::Value;
5
6use crate::extensions::JsonHelper;
7use crate::model_public::{Experiment, GrowthBookAttribute, GrowthBookAttributeValue};
8use crate::range::model::Range;
9
10#[derive(Deserialize, Clone, Default)]
11#[serde(rename_all = "camelCase")]
12pub struct GrowthBookResponse {
13 pub forced_variations: Option<HashMap<String, i64>>,
14 pub features: Option<HashMap<String, GrowthBookFeature>>,
15 pub encrypted_features: Option<String>,
16}
17
18#[derive(Deserialize, Clone, Debug)]
19#[serde(rename_all = "camelCase")]
20pub struct GrowthBookFeature {
21 pub default_value: Option<Value>,
22 pub rules: Option<Vec<GrowthBookFeatureRule>>,
23}
24
25#[derive(Deserialize, Clone, Debug)]
26#[serde(untagged)]
27pub enum GrowthBookFeatureRule {
29 Experiment(GrowthBookFeatureRuleExperiment),
30 Rollout(GrowthBookFeatureRuleRollout),
31 Force(GrowthBookFeatureRuleForce),
32 Parent(GrowthBookFeatureRuleParent),
33 Empty(Value),
34}
35
36#[derive(Deserialize, Clone, Debug)]
37#[serde(rename_all = "camelCase")]
38pub struct GrowthBookFeatureRuleForce {
39 pub force: Value,
40 pub coverage: Option<f32>,
41 range: Option<Vec<f32>>,
42 pub hash_version: Option<i64>,
43 pub filters: Option<Value>,
44 pub seed: Option<String>,
45 condition: Option<HashMap<String, Value>>,
46}
47
48#[derive(Deserialize, Clone, Debug)]
49#[serde(rename_all = "camelCase")]
50pub struct GrowthBookFeatureRuleParent {
51 pub parent_conditions: Vec<GrowthBookFeatureRuleParentData>,
52}
53
54#[derive(Deserialize, Clone, Debug)]
55#[serde(rename_all = "camelCase")]
56pub struct GrowthBookFeatureRuleParentData {
57 pub id: String,
58 condition: Option<HashMap<String, Value>>,
59 pub gate: bool,
60}
61
62#[derive(Deserialize, Clone, Debug)]
63#[serde(rename_all = "camelCase")]
64pub struct GrowthBookFeatureRuleRollout {
65 pub force: Value,
66 pub coverage: f32,
67 range: Option<Vec<f32>>,
68 condition: Option<HashMap<String, Value>>,
69 pub hash_attribute: Option<String>,
70 pub fallback_attribute: Option<String>,
71 pub hash_version: Option<i64>,
72}
73
74#[derive(Deserialize, Clone, Debug)]
75#[serde(rename_all = "camelCase")]
76pub struct GrowthBookFeatureRuleExperiment {
77 pub key: Option<String>,
78 pub variations: Vec<Value>,
79 name: Option<String>,
80 pub coverage: Option<f32>,
81 seed: Option<String>,
82 pub hash_version: Option<i64>,
83 pub hash_attribute: Option<String>,
84 pub fallback_attribute: Option<String>,
85 weights: Option<Vec<f32>>,
86 pub namespace: Option<Vec<Value>>,
87 pub ranges: Option<Vec<Vec<f32>>>,
88 pub meta: Option<Value>,
89 pub filters: Option<Value>,
90 pub condition: Option<Value>,
91}
92
93impl GrowthBookFeatureRuleParentData {
94 pub fn conditions(&self) -> Option<Vec<GrowthBookAttribute>> {
95 option_map_to_attributes(self.condition.clone())
96 }
97}
98
99impl GrowthBookFeatureRuleRollout {
100 pub fn conditions(&self) -> Option<Vec<GrowthBookAttribute>> {
101 option_map_to_attributes(self.condition.clone())
102 }
103
104 pub fn range(&self) -> Option<Range> {
105 Range::get_range(self.range.clone())
106 }
107
108 pub fn get_fallback_attribute(&self) -> String {
109 self.fallback_attribute.clone().unwrap_or(String::from("id"))
110 }
111}
112
113impl GrowthBookFeatureRuleForce {
114 pub fn conditions(&self) -> Option<Vec<GrowthBookAttribute>> {
115 option_map_to_attributes(self.condition.clone())
116 }
117
118 pub fn range(&self) -> Option<Range> {
119 Range::get_range(self.range.clone())
120 }
121
122 pub fn get_fallback_attribute(&self) -> String {
123 String::from("id")
124 }
125}
126
127impl GrowthBookFeatureRuleExperiment {
128 pub fn seed(
129 &self,
130 feature_name: &str,
131 ) -> String {
132 self.seed.clone().unwrap_or(self.key.clone().unwrap_or(feature_name.to_string()))
133 }
134
135 pub fn ranges(&self) -> Vec<Range> {
136 if let Some(ranges) = self.ranges.clone() {
137 ranges.iter().map(|range| Range { start: range[0], end: range[1] }).collect()
138 } else {
139 Range::get_bucket_range(self.variations.len() as i64, &self.coverage, self.weights.clone())
140 }
141 }
142
143 pub fn namespace_range(&self) -> Option<(String, Range)> {
144 self.namespace.as_ref().map(|namespace| {
145 (
146 namespace[0].force_string(""),
147 Range {
148 start: namespace[1].force_f32(0.0),
149 end: namespace[2].force_f32(1.0),
150 },
151 )
152 })
153 }
154
155 pub fn model_experiment(&self) -> Experiment {
156 Experiment {
157 name: self.name.clone(),
158 seed: self.seed.clone(),
159 hash_version: self.hash_version,
160 hash_attribute: self.hash_attribute.clone(),
161 namespace: self.namespace.clone(),
162 coverage: self.coverage,
163 ranges: self.ranges.clone(),
164 meta: self.meta.clone(),
165 filters: self.filters.clone(),
166 variations: self.variations.clone(),
167 weights: self.weights.clone(),
168 condition: self.condition.clone(),
169 }
170 }
171}
172
173pub fn option_map_to_attributes(option_map: Option<HashMap<String, Value>>) -> Option<Vec<GrowthBookAttribute>> {
174 option_map.map(|conditions| conditions.iter().map(|(k, v)| GrowthBookAttribute::new(k.clone(), GrowthBookAttributeValue::from(v.clone()))).collect())
175}