1#[allow(dead_code)]
7#[derive(Clone, PartialEq, Debug)]
8pub enum FlagValue {
9 Bool(bool),
10 Int(i64),
11 Float(f64),
12 Text(String),
13}
14
15#[allow(dead_code)]
16#[derive(Clone)]
17pub struct FeatureFlag {
18 pub name: String,
19 pub value: FlagValue,
20 pub description: String,
21 pub tags: Vec<String>,
22 pub override_env: Option<String>,
23}
24
25#[allow(dead_code)]
26pub struct FeatureFlagRegistry {
27 pub flags: Vec<FeatureFlag>,
28}
29
30#[allow(dead_code)]
31pub fn new_feature_registry() -> FeatureFlagRegistry {
32 FeatureFlagRegistry { flags: Vec::new() }
33}
34
35#[allow(dead_code)]
36pub fn register_flag(reg: &mut FeatureFlagRegistry, flag: FeatureFlag) {
37 reg.flags.push(flag);
38}
39
40#[allow(dead_code)]
41pub fn get_flag<'a>(reg: &'a FeatureFlagRegistry, name: &str) -> Option<&'a FeatureFlag> {
42 reg.flags.iter().find(|f| f.name == name)
43}
44
45#[allow(dead_code)]
46pub fn set_flag_value(reg: &mut FeatureFlagRegistry, name: &str, value: FlagValue) -> bool {
47 if let Some(flag) = reg.flags.iter_mut().find(|f| f.name == name) {
48 flag.value = value;
49 true
50 } else {
51 false
52 }
53}
54
55#[allow(dead_code)]
56pub fn is_enabled(reg: &FeatureFlagRegistry, name: &str) -> bool {
57 match get_flag(reg, name) {
58 Some(flag) => match &flag.value {
59 FlagValue::Bool(b) => *b,
60 _ => false,
61 },
62 None => false,
63 }
64}
65
66#[allow(dead_code)]
67pub fn flag_count(reg: &FeatureFlagRegistry) -> usize {
68 reg.flags.len()
69}
70
71#[allow(dead_code)]
72pub fn flags_with_tag<'a>(reg: &'a FeatureFlagRegistry, tag: &str) -> Vec<&'a FeatureFlag> {
73 reg.flags
74 .iter()
75 .filter(|f| f.tags.iter().any(|t| t == tag))
76 .collect()
77}
78
79#[allow(dead_code)]
80pub fn all_enabled_flags(reg: &FeatureFlagRegistry) -> Vec<&FeatureFlag> {
81 reg.flags
82 .iter()
83 .filter(|f| matches!(&f.value, FlagValue::Bool(true)))
84 .collect()
85}
86
87#[allow(dead_code)]
88pub fn default_bool_flag(name: &str, default: bool, description: &str) -> FeatureFlag {
89 FeatureFlag {
90 name: name.to_string(),
91 value: FlagValue::Bool(default),
92 description: description.to_string(),
93 tags: Vec::new(),
94 override_env: None,
95 }
96}
97
98#[allow(dead_code)]
99pub fn default_int_flag(name: &str, default: i64, description: &str) -> FeatureFlag {
100 FeatureFlag {
101 name: name.to_string(),
102 value: FlagValue::Int(default),
103 description: description.to_string(),
104 tags: Vec::new(),
105 override_env: None,
106 }
107}
108
109#[allow(dead_code)]
110pub fn get_flag_bool(reg: &FeatureFlagRegistry, name: &str) -> Option<bool> {
111 match get_flag(reg, name) {
112 Some(flag) => match &flag.value {
113 FlagValue::Bool(b) => Some(*b),
114 _ => None,
115 },
116 None => None,
117 }
118}
119
120#[allow(dead_code)]
121pub fn get_flag_int(reg: &FeatureFlagRegistry, name: &str) -> Option<i64> {
122 match get_flag(reg, name) {
123 Some(flag) => match &flag.value {
124 FlagValue::Int(i) => Some(*i),
125 _ => None,
126 },
127 None => None,
128 }
129}
130
131#[allow(dead_code)]
132pub fn feature_registry_to_json(reg: &FeatureFlagRegistry) -> String {
133 let mut out = String::from("{\"flags\":[");
134 for (i, flag) in reg.flags.iter().enumerate() {
135 if i > 0 {
136 out.push(',');
137 }
138 let val_str = match &flag.value {
139 FlagValue::Bool(b) => format!("{}", b),
140 FlagValue::Int(n) => format!("{}", n),
141 FlagValue::Float(f) => format!("{}", f),
142 FlagValue::Text(s) => format!("\"{}\"", s),
143 };
144 out.push_str(&format!(
145 "{{\"name\":\"{}\",\"value\":{},\"description\":\"{}\"}}",
146 flag.name, val_str, flag.description
147 ));
148 }
149 out.push_str("]}");
150 out
151}
152
153#[allow(dead_code)]
154pub fn remove_flag(reg: &mut FeatureFlagRegistry, name: &str) -> bool {
155 let before = reg.flags.len();
156 reg.flags.retain(|f| f.name != name);
157 reg.flags.len() < before
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_new_feature_registry() {
166 let reg = new_feature_registry();
167 assert_eq!(flag_count(®), 0);
168 }
169
170 #[test]
171 fn test_register_flag() {
172 let mut reg = new_feature_registry();
173 let flag = default_bool_flag("debug", true, "Enable debug mode");
174 register_flag(&mut reg, flag);
175 assert_eq!(flag_count(®), 1);
176 }
177
178 #[test]
179 fn test_get_flag() {
180 let mut reg = new_feature_registry();
181 let flag = default_bool_flag("feature_x", false, "Feature X");
182 register_flag(&mut reg, flag);
183 assert!(get_flag(®, "feature_x").is_some());
184 assert!(get_flag(®, "missing").is_none());
185 }
186
187 #[test]
188 fn test_set_flag_value() {
189 let mut reg = new_feature_registry();
190 register_flag(&mut reg, default_bool_flag("beta", false, "Beta"));
191 let ok = set_flag_value(&mut reg, "beta", FlagValue::Bool(true));
192 assert!(ok);
193 assert!(is_enabled(®, "beta"));
194 let not_ok = set_flag_value(&mut reg, "nonexistent", FlagValue::Bool(true));
195 assert!(!not_ok);
196 }
197
198 #[test]
199 fn test_is_enabled() {
200 let mut reg = new_feature_registry();
201 register_flag(&mut reg, default_bool_flag("on", true, "On"));
202 register_flag(&mut reg, default_bool_flag("off", false, "Off"));
203 assert!(is_enabled(®, "on"));
204 assert!(!is_enabled(®, "off"));
205 assert!(!is_enabled(®, "missing"));
206 }
207
208 #[test]
209 fn test_flag_count() {
210 let mut reg = new_feature_registry();
211 assert_eq!(flag_count(®), 0);
212 register_flag(&mut reg, default_bool_flag("a", true, "A"));
213 register_flag(&mut reg, default_bool_flag("b", false, "B"));
214 assert_eq!(flag_count(®), 2);
215 }
216
217 #[test]
218 fn test_flags_with_tag() {
219 let mut reg = new_feature_registry();
220 let mut flag = default_bool_flag("experimental", true, "Experimental feature");
221 flag.tags.push("experimental".to_string());
222 register_flag(&mut reg, flag);
223 register_flag(
224 &mut reg,
225 default_bool_flag("stable", true, "Stable feature"),
226 );
227 let experimental = flags_with_tag(®, "experimental");
228 assert_eq!(experimental.len(), 1);
229 assert_eq!(experimental[0].name, "experimental");
230 }
231
232 #[test]
233 fn test_all_enabled_flags() {
234 let mut reg = new_feature_registry();
235 register_flag(&mut reg, default_bool_flag("enabled1", true, "E1"));
236 register_flag(&mut reg, default_bool_flag("disabled1", false, "D1"));
237 register_flag(&mut reg, default_bool_flag("enabled2", true, "E2"));
238 let enabled = all_enabled_flags(®);
239 assert_eq!(enabled.len(), 2);
240 }
241
242 #[test]
243 fn test_get_flag_bool() {
244 let mut reg = new_feature_registry();
245 register_flag(&mut reg, default_bool_flag("flag", true, "F"));
246 assert_eq!(get_flag_bool(®, "flag"), Some(true));
247 assert_eq!(get_flag_bool(®, "missing"), None);
248 }
249
250 #[test]
251 fn test_get_flag_int() {
252 let mut reg = new_feature_registry();
253 register_flag(&mut reg, default_int_flag("max_count", 42, "Max count"));
254 assert_eq!(get_flag_int(®, "max_count"), Some(42));
255 assert_eq!(get_flag_int(®, "missing"), None);
256 }
257
258 #[test]
259 fn test_get_flag_int_wrong_type() {
260 let mut reg = new_feature_registry();
261 register_flag(&mut reg, default_bool_flag("flag", true, "F"));
262 assert_eq!(get_flag_int(®, "flag"), None);
263 }
264
265 #[test]
266 fn test_feature_registry_to_json() {
267 let mut reg = new_feature_registry();
268 register_flag(&mut reg, default_bool_flag("a", true, "A flag"));
269 let json = feature_registry_to_json(®);
270 assert!(!json.is_empty());
271 assert!(json.contains("flags"));
272 assert!(json.contains("\"a\""));
273 }
274
275 #[test]
276 fn test_remove_flag() {
277 let mut reg = new_feature_registry();
278 register_flag(&mut reg, default_bool_flag("temp", true, "Temporary"));
279 assert_eq!(flag_count(®), 1);
280 let removed = remove_flag(&mut reg, "temp");
281 assert!(removed);
282 assert_eq!(flag_count(®), 0);
283 let not_removed = remove_flag(&mut reg, "temp");
284 assert!(!not_removed);
285 }
286
287 #[test]
288 fn test_flag_value_variants() {
289 let mut reg = new_feature_registry();
290 register_flag(
291 &mut reg,
292 FeatureFlag {
293 name: "text_flag".to_string(),
294 value: FlagValue::Text("hello".to_string()),
295 description: "Text".to_string(),
296 tags: Vec::new(),
297 override_env: None,
298 },
299 );
300 register_flag(
301 &mut reg,
302 FeatureFlag {
303 name: "float_flag".to_string(),
304 value: FlagValue::Float(2.78),
305 description: "Float".to_string(),
306 tags: Vec::new(),
307 override_env: Some("MY_FLOAT_FLAG".to_string()),
308 },
309 );
310 let json = feature_registry_to_json(®);
311 assert!(json.contains("text_flag"));
312 assert!(json.contains("float_flag"));
313 }
314}