Skip to main content

behavior_actors/operations/
features.rs

1//! Feature-policy products for use with versioned configuration.
2
3/// Exhaustive state of one feature.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum FeatureStatus {
6    /// The feature is enabled.
7    Enabled,
8    /// The feature is disabled.
9    Disabled,
10}
11
12/// One member of a statically known application feature identity type.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct Feature<F> {
15    /// Application-defined feature identity.
16    pub feature: F,
17    /// Explicit state; absence never implies a state.
18    pub status: FeatureStatus,
19}
20
21/// A duplicate-free, deterministically ordered feature policy product.
22///
23/// Construction keeps the final declaration for a duplicate identity at its
24/// original position. This is deliberate Bombay policy and makes one feature
25/// incapable of occupying contradictory states in the same configuration.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct FeatureSet<F> {
28    features: Vec<Feature<F>>,
29}
30
31impl<F: Eq> FeatureSet<F> {
32    /// Construct one coherent set from ordered declarations.
33    #[must_use]
34    pub fn new(features: impl IntoIterator<Item = Feature<F>>) -> Self {
35        let mut normalized: Vec<Feature<F>> = Vec::new();
36        for candidate in features {
37            if let Some(existing) = normalized
38                .iter_mut()
39                .find(|existing| existing.feature == candidate.feature)
40            {
41                existing.status = candidate.status;
42            } else {
43                normalized.push(candidate);
44            }
45        }
46        Self {
47            features: normalized,
48        }
49    }
50
51    /// Borrow declarations in deterministic first-identity order.
52    #[must_use]
53    pub fn features(&self) -> &[Feature<F>] {
54        &self.features
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn duplicate_identity_replaces_status_without_reordering() {
64        let set = FeatureSet::new([
65            Feature {
66                feature: 1,
67                status: FeatureStatus::Disabled,
68            },
69            Feature {
70                feature: 2,
71                status: FeatureStatus::Enabled,
72            },
73            Feature {
74                feature: 1,
75                status: FeatureStatus::Enabled,
76            },
77        ]);
78        assert_eq!(
79            set.features(),
80            [
81                Feature {
82                    feature: 1,
83                    status: FeatureStatus::Enabled
84                },
85                Feature {
86                    feature: 2,
87                    status: FeatureStatus::Enabled
88                }
89            ]
90        );
91    }
92}