protocol_droid/domain/ir/
features.rs1use crate::domain::Feature;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
26pub struct FeatureSet {
27 bits: u128,
28}
29
30impl FeatureSet {
31 #[must_use]
34 pub const fn empty() -> Self {
35 Self { bits: 0 }
36 }
37
38 #[must_use]
40 pub fn with(self, feature: Feature) -> Self {
41 Self {
42 bits: self.bits | bit_of(feature),
43 }
44 }
45
46 pub fn insert(&mut self, feature: Feature) {
48 self.bits |= bit_of(feature);
49 }
50
51 #[must_use]
53 pub fn contains(self, feature: Feature) -> bool {
54 self.bits & bit_of(feature) != 0
55 }
56
57 #[must_use]
59 pub const fn is_empty(self) -> bool {
60 self.bits == 0
61 }
62
63 #[must_use]
65 pub const fn len(self) -> u32 {
66 self.bits.count_ones()
67 }
68
69 #[must_use]
75 pub const fn union(self, other: Self) -> Self {
76 Self {
77 bits: self.bits | other.bits,
78 }
79 }
80
81 #[must_use]
83 pub fn union_of(features: impl IntoIterator<Item = Feature>) -> Self {
84 features.into_iter().fold(Self::empty(), Self::with)
85 }
86
87 #[must_use]
89 pub const fn difference(self, other: Self) -> Self {
90 Self {
91 bits: self.bits & !other.bits,
92 }
93 }
94
95 #[must_use]
105 pub const fn from_bits(bits: u128) -> Self {
106 Self { bits }
107 }
108
109 #[must_use]
111 pub const fn bits(self) -> u128 {
112 self.bits
113 }
114
115 #[must_use]
119 pub const fn is_subset_of(self, other: Self) -> bool {
120 self.bits & !other.bits == 0
121 }
122
123 pub fn iter(self) -> impl Iterator<Item = Feature> {
125 Feature::ALL.into_iter().filter(move |f| self.contains(*f))
126 }
127}
128
129fn bit_of(feature: Feature) -> u128 {
137 let name = feature.as_str();
138 let mut index = 0;
139 while let Some(candidate) = Feature::ALL.get(index) {
140 if candidate.as_str() == name {
141 return 1u128 << index;
142 }
143 index += 1;
144 }
145 0
146}
147
148#[cfg(test)]
149mod tests {
150 use super::FeatureSet;
151 use crate::domain::Feature;
152
153 #[test]
154 fn an_empty_set_needs_nothing() {
155 let empty = FeatureSet::empty();
156 assert!(empty.is_empty());
157 assert_eq!(empty.len(), 0);
158 assert!(!empty.contains(Feature::Tools));
159 }
160
161 #[test]
162 fn a_single_feature_set_contains_exactly_that() {
163 let set = FeatureSet::empty().with(Feature::Tools);
164 assert!(set.contains(Feature::Tools));
165 assert!(!set.contains(Feature::Prompts));
166 assert_eq!(set.len(), 1);
167 }
168
169 #[test]
170 fn union_merges_and_deduplicates() {
171 let a = FeatureSet::empty()
172 .with(Feature::Tools)
173 .with(Feature::Prompts);
174 let b = FeatureSet::empty()
175 .with(Feature::Prompts)
176 .with(Feature::Resources);
177 let union = a.union(b);
178 assert_eq!(union.len(), 3);
179 for feature in [Feature::Tools, Feature::Prompts, Feature::Resources] {
180 assert!(union.contains(feature), "{feature}");
181 }
182 }
183
184 #[test]
185 fn union_of_iterates() {
186 let set = FeatureSet::union_of([Feature::Tools, Feature::Prompts]);
187 assert_eq!(set.len(), 2);
188 assert!(FeatureSet::union_of([]).is_empty());
189 }
190
191 #[test]
194 fn subset_reflects_inclusion() {
195 let both = FeatureSet::empty()
196 .with(Feature::Tools)
197 .with(Feature::Prompts);
198 let one = FeatureSet::empty().with(Feature::Tools);
199
200 assert!(one.is_subset_of(both));
201 assert!(both.is_subset_of(both));
202 assert!(FeatureSet::empty().is_subset_of(both));
204 assert!(FeatureSet::empty().is_subset_of(one));
205 assert!(!both.is_subset_of(one));
206 }
207
208 #[test]
209 fn iter_yields_declaration_order() {
210 let set = FeatureSet::empty()
211 .with(Feature::Extensions)
212 .with(Feature::Tools);
213 let yielded: Vec<Feature> = set.iter().collect();
214 assert_eq!(yielded, vec![Feature::Tools, Feature::Extensions]);
215 }
216
217 #[test]
218 fn every_feature_gets_a_distinct_bit() {
219 let mut seen = Vec::new();
220 for feature in Feature::ALL {
221 let set = FeatureSet::empty().with(feature);
222 assert_eq!(set.len(), 1, "{feature} collided with another bit");
223 assert!(set.contains(feature), "{feature} is missing");
224 assert!(!seen.contains(&feature), "{feature} twice");
225 seen.push(feature);
226 }
227 assert_eq!(seen.len(), Feature::ALL.len());
228 }
229}