use ahash::AHashMap;
use rialo_feature_management_interface::state::FeaturesState;
pub type Slot = u64;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ActiveFeatures {
active: AHashMap<String, Slot>,
}
impl ActiveFeatures {
pub fn new() -> Self {
Self::default()
}
pub fn from_features_state(
state: &FeaturesState,
current_time_ms: u64,
snapshot_slot: Slot,
) -> Self {
let mut active: AHashMap<String, Slot> = AHashMap::new();
for name in state.entries().keys() {
if state.is_active(name, current_time_ms) {
active.insert(name.clone(), snapshot_slot);
}
}
Self::from_map(active)
}
pub(crate) fn from_map(active: AHashMap<String, Slot>) -> Self {
Self { active }
}
#[cfg(any(test, feature = "dev-context-only-utils"))]
pub fn with_overrides(features: &[&str]) -> Self {
let active: AHashMap<String, Slot> = features
.iter()
.map(|name| ((*name).to_string(), 0))
.collect();
Self::from_map(active)
}
pub fn is_active(&self, feature: &str) -> bool {
self.active.contains_key(feature)
}
pub fn snapshot_slot(&self, feature: &str) -> Option<Slot> {
self.active.get(feature).copied()
}
pub fn len(&self) -> usize {
self.active.len()
}
pub fn is_empty(&self) -> bool {
self.active.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, Slot)> + '_ {
self.active.iter().map(|(k, v)| (k.as_str(), *v))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make(entries: &[(&str, Slot)]) -> ActiveFeatures {
let mut m = AHashMap::new();
for (k, v) in entries {
m.insert((*k).to_string(), *v);
}
ActiveFeatures::from_map(m)
}
#[test]
fn test_empty_is_empty() {
let a = ActiveFeatures::new();
assert!(a.is_empty());
assert_eq!(a.len(), 0);
assert!(!a.is_active("anything"));
assert_eq!(a.snapshot_slot("anything"), None);
}
#[test]
fn test_is_active_reflects_membership() {
let a = make(&[("foo", 42), ("bar", 100)]);
assert!(a.is_active("foo"));
assert!(a.is_active("bar"));
assert!(!a.is_active("baz"));
}
#[test]
fn test_snapshot_slot_returns_recorded_value() {
let a = make(&[("foo", 42)]);
assert_eq!(a.snapshot_slot("foo"), Some(42));
assert_eq!(a.snapshot_slot("missing"), None);
}
#[test]
fn test_iter_covers_all_entries() {
let a = make(&[("foo", 1), ("bar", 2)]);
let mut seen: Vec<(&str, Slot)> = a.iter().collect();
seen.sort_by_key(|(k, _)| *k);
assert_eq!(seen, vec![("bar", 2), ("foo", 1)]);
}
#[test]
fn test_with_overrides_marks_listed_features_active() {
let a = ActiveFeatures::with_overrides(&["foo", "bar"]);
assert!(a.is_active("foo"));
assert!(a.is_active("bar"));
assert!(!a.is_active("baz"));
assert_eq!(a.snapshot_slot("foo"), Some(0));
}
#[test]
fn test_with_overrides_empty_yields_empty() {
let a = ActiveFeatures::with_overrides(&[]);
assert!(a.is_empty());
}
}