Skip to main content

hedgehog_rs/data/
feature_flag.rs

1use std::collections::HashMap;
2
3use serde_json::Value;
4
5#[derive(Debug, Clone)]
6pub struct FeatureFlagCollection {
7    pub(crate) flags: HashMap<String, FeatureFlag>,
8}
9
10impl FeatureFlagCollection {
11    pub(crate) fn new(flags: HashMap<String, FeatureFlag>) -> Self {
12        Self { flags }
13    }
14
15    pub fn iter(&self) -> impl Iterator<Item = (&str, &FeatureFlag)> {
16        self.flags.iter().map(|(k, v)| (k.as_str(), v))
17    }
18
19    pub fn get(&self, key: &str) -> Option<&FeatureFlag> {
20        self.flags.get(key)
21    }
22
23    pub fn get_str_flag(&self, key: &str) -> Option<&str> {
24        self.get(key).and_then(|flag| flag.str())
25    }
26
27    pub fn get_bool_flag(&self, key: &str) -> Option<bool> {
28        self.get(key).and_then(|flag| flag.bool())
29    }
30
31    pub fn get_int_flag(&self, key: &str) -> Option<i64> {
32        self.get(key).and_then(|flag| flag.int())
33    }
34
35    pub fn get_json_flag(&self, key: &str) -> Option<&Value> {
36        self.get(key).and_then(|flag| flag.json())
37    }
38
39    pub fn get_typed_json_flag<T>(&self, key: &str) -> Option<T>
40    where
41        T: serde::de::DeserializeOwned,
42    {
43        self.get_json_flag(key)
44            .and_then(|json| serde_json::from_value(json.clone()).ok())
45    }
46}
47
48impl std::ops::Index<&str> for FeatureFlagCollection {
49    type Output = FeatureFlag;
50
51    fn index(&self, key: &str) -> &Self::Output {
52        self.get(key).expect("no such feature flag")
53    }
54}
55
56#[derive(Debug, Clone)]
57pub struct FeatureFlag {
58    pub(crate) variant: FeatureFlagData,
59    pub(crate) payload: Option<FeatureFlagData>,
60}
61
62impl FeatureFlag {
63    pub fn variant(&self) -> &FeatureFlagData {
64        &self.variant
65    }
66
67    pub fn variant_as_str(&self) -> String {
68        match &self.variant {
69            FeatureFlagData::Boolean(b) => b.to_string(),
70            FeatureFlagData::Integer(i) => i.to_string(),
71            FeatureFlagData::String(s) => s.clone(),
72            FeatureFlagData::Json(v) => v.to_string(),
73        }
74    }
75
76    pub fn variant_as_bool(&self) -> bool {
77        match &self.variant {
78            FeatureFlagData::Boolean(b) => *b,
79            FeatureFlagData::String(s) => s.parse().unwrap_or(false),
80            FeatureFlagData::Integer(i) => *i != 0,
81            FeatureFlagData::Json(_) => false,
82        }
83    }
84
85    pub fn payload(&self) -> Option<&FeatureFlagData> {
86        self.payload.as_ref()
87    }
88
89    pub fn str(&self) -> Option<&str> {
90        match &self.payload {
91            Some(FeatureFlagData::String(s)) => Some(s),
92            _ => None,
93        }
94    }
95
96    pub fn int(&self) -> Option<i64> {
97        match &self.payload {
98            Some(FeatureFlagData::Integer(i)) => Some(*i),
99            _ => None,
100        }
101    }
102
103    pub fn bool(&self) -> Option<bool> {
104        match &self.payload {
105            Some(FeatureFlagData::Boolean(b)) => Some(*b),
106            _ => None,
107        }
108    }
109
110    pub fn json(&self) -> Option<&Value> {
111        match &self.payload {
112            Some(FeatureFlagData::Json(v)) => Some(v),
113            _ => None,
114        }
115    }
116}
117
118#[derive(Debug, Clone)]
119pub enum FeatureFlagData {
120    Boolean(bool),
121    Integer(i64),
122    String(String),
123    Json(Value),
124}
125
126impl From<Value> for FeatureFlagData {
127    fn from(value: Value) -> Self {
128        match value {
129            Value::Bool(b) => Self::Boolean(b),
130            Value::Number(n) => {
131                if let Some(i) = n.as_i64() {
132                    Self::Integer(i)
133                } else {
134                    Self::Json(Value::Number(n))
135                }
136            }
137            Value::String(s) => Self::String(s),
138            other => Self::Json(other),
139        }
140    }
141}