use crate::domain::Feature;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct FeatureSet {
bits: u128,
}
impl FeatureSet {
#[must_use]
pub const fn empty() -> Self {
Self { bits: 0 }
}
#[must_use]
pub fn with(self, feature: Feature) -> Self {
Self {
bits: self.bits | bit_of(feature),
}
}
pub fn insert(&mut self, feature: Feature) {
self.bits |= bit_of(feature);
}
#[must_use]
pub fn contains(self, feature: Feature) -> bool {
self.bits & bit_of(feature) != 0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.bits == 0
}
#[must_use]
pub const fn len(self) -> u32 {
self.bits.count_ones()
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
#[must_use]
pub fn union_of(features: impl IntoIterator<Item = Feature>) -> Self {
features.into_iter().fold(Self::empty(), Self::with)
}
#[must_use]
pub const fn difference(self, other: Self) -> Self {
Self {
bits: self.bits & !other.bits,
}
}
#[must_use]
pub const fn from_bits(bits: u128) -> Self {
Self { bits }
}
#[must_use]
pub const fn bits(self) -> u128 {
self.bits
}
#[must_use]
pub const fn is_subset_of(self, other: Self) -> bool {
self.bits & !other.bits == 0
}
pub fn iter(self) -> impl Iterator<Item = Feature> {
Feature::ALL.into_iter().filter(move |f| self.contains(*f))
}
}
fn bit_of(feature: Feature) -> u128 {
let name = feature.as_str();
let mut index = 0;
while let Some(candidate) = Feature::ALL.get(index) {
if candidate.as_str() == name {
return 1u128 << index;
}
index += 1;
}
0
}
#[cfg(test)]
mod tests {
use super::FeatureSet;
use crate::domain::Feature;
#[test]
fn an_empty_set_needs_nothing() {
let empty = FeatureSet::empty();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert!(!empty.contains(Feature::Tools));
}
#[test]
fn a_single_feature_set_contains_exactly_that() {
let set = FeatureSet::empty().with(Feature::Tools);
assert!(set.contains(Feature::Tools));
assert!(!set.contains(Feature::Prompts));
assert_eq!(set.len(), 1);
}
#[test]
fn union_merges_and_deduplicates() {
let a = FeatureSet::empty()
.with(Feature::Tools)
.with(Feature::Prompts);
let b = FeatureSet::empty()
.with(Feature::Prompts)
.with(Feature::Resources);
let union = a.union(b);
assert_eq!(union.len(), 3);
for feature in [Feature::Tools, Feature::Prompts, Feature::Resources] {
assert!(union.contains(feature), "{feature}");
}
}
#[test]
fn union_of_iterates() {
let set = FeatureSet::union_of([Feature::Tools, Feature::Prompts]);
assert_eq!(set.len(), 2);
assert!(FeatureSet::union_of([]).is_empty());
}
#[test]
fn subset_reflects_inclusion() {
let both = FeatureSet::empty()
.with(Feature::Tools)
.with(Feature::Prompts);
let one = FeatureSet::empty().with(Feature::Tools);
assert!(one.is_subset_of(both));
assert!(both.is_subset_of(both));
assert!(FeatureSet::empty().is_subset_of(both));
assert!(FeatureSet::empty().is_subset_of(one));
assert!(!both.is_subset_of(one));
}
#[test]
fn iter_yields_declaration_order() {
let set = FeatureSet::empty()
.with(Feature::Extensions)
.with(Feature::Tools);
let yielded: Vec<Feature> = set.iter().collect();
assert_eq!(yielded, vec![Feature::Tools, Feature::Extensions]);
}
#[test]
fn every_feature_gets_a_distinct_bit() {
let mut seen = Vec::new();
for feature in Feature::ALL {
let set = FeatureSet::empty().with(feature);
assert_eq!(set.len(), 1, "{feature} collided with another bit");
assert!(set.contains(feature), "{feature} is missing");
assert!(!seen.contains(&feature), "{feature} twice");
seen.push(feature);
}
assert_eq!(seen.len(), Feature::ALL.len());
}
}