rullst_core/feature/
memory.rs1use async_trait::async_trait;
2use dashmap::DashMap;
3
4use super::driver::FeatureDriver;
5use super::resolvers::{calculate_hash_bucket, resolve_variant};
6
7struct MemoryFlagRule {
10 enabled: bool,
11 rollout_percentage: Option<u32>,
12 variants: Option<Vec<(String, u32)>>,
13}
14
15#[non_exhaustive]
17pub struct MemoryFeatureDriver {
18 rules: DashMap<String, MemoryFlagRule>,
19}
20
21impl MemoryFeatureDriver {
22 pub fn new() -> Self {
24 Self {
25 rules: DashMap::new(),
26 }
27 }
28
29 pub fn override_enabled(&self, flag: &str, enabled: bool) {
31 self.rules.insert(
32 flag.to_string(),
33 MemoryFlagRule {
34 enabled,
35 rollout_percentage: None,
36 variants: None,
37 },
38 );
39 }
40
41 pub fn override_rollout(&self, flag: &str, percentage: u32) {
43 self.rules.insert(
44 flag.to_string(),
45 MemoryFlagRule {
46 enabled: true,
47 rollout_percentage: Some(percentage),
48 variants: None,
49 },
50 );
51 }
52
53 pub fn override_variants(&self, flag: &str, variants: Vec<(String, u32)>) {
55 self.rules.insert(
56 flag.to_string(),
57 MemoryFlagRule {
58 enabled: true,
59 rollout_percentage: None,
60 variants: Some(variants),
61 },
62 );
63 }
64}
65
66impl Default for MemoryFeatureDriver {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72#[async_trait]
73impl FeatureDriver for MemoryFeatureDriver {
74 async fn enabled(&self, flag: &str) -> Option<bool> {
75 self.rules
76 .get(flag)
77 .map(|r| r.enabled && r.rollout_percentage.is_none())
78 }
79
80 #[cfg_attr(mutants, mutants::skip)]
81 async fn enabled_for(&self, flag: &str, identifier: &str) -> Option<bool> {
82 let rule = self.rules.get(flag)?;
83 if !rule.enabled {
84 return Some(false);
85 }
86 if let Some(pct) = rule.rollout_percentage {
87 let bucket = calculate_hash_bucket(flag, identifier);
88 return Some(bucket < pct);
89 }
90 Some(rule.enabled)
91 }
92
93 #[cfg_attr(mutants, mutants::skip)]
94 async fn variant(&self, flag: &str, identifier: &str) -> Option<String> {
95 let rule = self.rules.get(flag)?;
96 if !rule.enabled {
97 return Some("disabled".to_string());
98 }
99 if let Some(ref variants) = rule.variants {
100 let bucket = calculate_hash_bucket(flag, identifier);
101 return resolve_variant(variants, bucket);
102 }
103 if let Some(pct) = rule.rollout_percentage {
104 let bucket = calculate_hash_bucket(flag, identifier);
105 return Some(if bucket < pct {
106 "enabled".to_string()
107 } else {
108 "disabled".to_string()
109 });
110 }
111 Some(if rule.enabled {
112 "enabled".to_string()
113 } else {
114 "disabled".to_string()
115 })
116 }
117}