growthbook_rust/
dto.rs

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)]
27// needs to be in this order
28pub 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}