use std::collections::HashMap;
pub trait FeatureFlags {
fn is_enabled(&self, name: &str) -> Option<bool>;
fn set(&mut self, name: &str, enabled: bool) -> bool;
fn all_flags() -> &'static [&'static str]
where
Self: Sized;
fn enable(&mut self, name: &str) -> bool {
self.set(name, true)
}
fn disable(&mut self, name: &str) -> bool {
self.set(name, false)
}
fn toggle(&mut self, name: &str) -> Option<bool> {
let current = self.is_enabled(name)?;
let new_state = !current;
self.set(name, new_state);
Some(new_state)
}
fn to_map(&self) -> HashMap<String, bool>
where
Self: Sized,
{
Self::all_flags()
.iter()
.filter_map(|name| self.is_enabled(name).map(|v| ((*name).to_string(), v)))
.collect()
}
fn load_from_map(&mut self, map: &HashMap<String, bool>) -> usize {
let mut count = 0;
for (name, enabled) in map {
if self.set(name, *enabled) {
count += 1;
}
}
count
}
}
#[derive(Debug, Clone, Default)]
pub struct DynamicFeatures {
flags: HashMap<String, bool>,
}
impl DynamicFeatures {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, name: impl Into<String>, default: bool) {
self.flags.insert(name.into(), default);
}
pub fn get(&self, name: &str) -> bool {
self.flags.get(name).copied().unwrap_or(false)
}
pub fn has(&self, name: &str) -> bool {
self.flags.contains_key(name)
}
pub fn flag_names(&self) -> impl Iterator<Item = &str> {
self.flags.keys().map(|s| s.as_str())
}
pub fn enable(&mut self, name: &str) -> bool {
if let Some(v) = self.flags.get_mut(name) {
*v = true;
true
} else {
false
}
}
pub fn disable(&mut self, name: &str) -> bool {
if let Some(v) = self.flags.get_mut(name) {
*v = false;
true
} else {
false
}
}
pub fn toggle(&mut self, name: &str) -> Option<bool> {
if let Some(v) = self.flags.get_mut(name) {
*v = !*v;
Some(*v)
} else {
None
}
}
pub fn load(&mut self, map: HashMap<String, bool>) {
for (name, enabled) in map {
self.flags.insert(name, enabled);
}
}
pub fn export(&self) -> HashMap<String, bool> {
self.flags.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct TestFeatures {
dark_mode: bool,
vim_bindings: bool,
}
impl FeatureFlags for TestFeatures {
fn is_enabled(&self, name: &str) -> Option<bool> {
match name {
"dark_mode" => Some(self.dark_mode),
"vim_bindings" => Some(self.vim_bindings),
_ => None,
}
}
fn set(&mut self, name: &str, enabled: bool) -> bool {
match name {
"dark_mode" => {
self.dark_mode = enabled;
true
}
"vim_bindings" => {
self.vim_bindings = enabled;
true
}
_ => false,
}
}
fn all_flags() -> &'static [&'static str] {
&["dark_mode", "vim_bindings"]
}
}
#[test]
fn test_feature_flags_trait() {
let mut features = TestFeatures::default();
assert_eq!(features.is_enabled("dark_mode"), Some(false));
assert_eq!(features.is_enabled("vim_bindings"), Some(false));
assert_eq!(features.is_enabled("unknown"), None);
features.enable("dark_mode");
assert_eq!(features.is_enabled("dark_mode"), Some(true));
features.disable("dark_mode");
assert_eq!(features.is_enabled("dark_mode"), Some(false));
let new_state = features.toggle("vim_bindings");
assert_eq!(new_state, Some(true));
assert_eq!(features.is_enabled("vim_bindings"), Some(true));
}
#[test]
fn test_feature_flags_to_map() {
let features = TestFeatures {
dark_mode: true,
..Default::default()
};
let map = features.to_map();
assert_eq!(map.get("dark_mode"), Some(&true));
assert_eq!(map.get("vim_bindings"), Some(&false));
}
#[test]
fn test_feature_flags_load_from_map() {
let mut features = TestFeatures::default();
let mut map = HashMap::new();
map.insert("dark_mode".to_string(), true);
map.insert("vim_bindings".to_string(), true);
map.insert("unknown".to_string(), true);
let count = features.load_from_map(&map);
assert_eq!(count, 2);
assert!(features.dark_mode);
assert!(features.vim_bindings);
}
#[test]
fn test_dynamic_features() {
let mut features = DynamicFeatures::new();
features.register("dark_mode", true);
features.register("experimental", false);
assert!(features.get("dark_mode"));
assert!(!features.get("experimental"));
assert!(!features.get("unknown"));
features.toggle("experimental");
assert!(features.get("experimental"));
features.disable("dark_mode");
assert!(!features.get("dark_mode"));
}
#[test]
fn test_dynamic_features_load() {
let mut features = DynamicFeatures::new();
let mut map = HashMap::new();
map.insert("feature_a".to_string(), true);
map.insert("feature_b".to_string(), false);
features.load(map);
assert!(features.get("feature_a"));
assert!(!features.get("feature_b"));
assert!(features.has("feature_a"));
assert!(features.has("feature_b"));
}
#[test]
fn test_dynamic_features_export() {
let mut features = DynamicFeatures::new();
features.register("a", true);
features.register("b", false);
let exported = features.export();
assert_eq!(exported.len(), 2);
assert_eq!(exported.get("a"), Some(&true));
assert_eq!(exported.get("b"), Some(&false));
}
}