hedgehog_rs/data/
feature_flag.rs1use 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}