crawlkit_engine/
feature_flags.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct FeatureFlags {
13 #[serde(flatten)]
14 flags: HashMap<String, bool>,
15}
16
17impl FeatureFlags {
18 #[must_use]
20 pub fn new() -> Self {
21 Self {
22 flags: HashMap::new(),
23 }
24 }
25
26 pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
31 toml::from_str(toml_str)
32 }
33
34 pub fn set(&mut self, key: &str, value: bool) {
36 self.flags.insert(key.to_string(), value);
37 }
38
39 #[must_use]
41 pub fn get(&self, key: &str) -> bool {
42 *self.flags.get(key).unwrap_or(&false)
43 }
44
45 #[must_use]
47 pub fn has(&self, key: &str) -> bool {
48 self.flags.contains_key(key)
49 }
50
51 #[must_use]
53 pub fn all(&self) -> &HashMap<String, bool> {
54 &self.flags
55 }
56
57 #[must_use]
59 pub fn merge(mut self, other: FeatureFlags) -> Self {
60 for (k, v) in other.flags {
61 self.flags.insert(k, v);
62 }
63 self
64 }
65}
66
67impl Default for FeatureFlags {
68 fn default() -> Self {
69 let mut flags = HashMap::new();
70 flags.insert("js_rendering".to_string(), false);
72 flags.insert("ai_analyzers".to_string(), true);
73 flags.insert("wasm_analyzers".to_string(), true);
74 flags.insert("backlink_analysis".to_string(), false);
75 flags.insert("rum_integration".to_string(), false);
76 flags.insert("encryption_at_rest".to_string(), false);
77 flags.insert("audit_trail".to_string(), true);
78 flags.insert("observability".to_string(), true);
79 Self { flags }
80 }
81}
82
83#[derive(Clone)]
85pub struct SharedFeatureFlags {
86 flags: Arc<RwLock<FeatureFlags>>,
87}
88
89impl SharedFeatureFlags {
90 #[must_use]
92 pub fn new(flags: FeatureFlags) -> Self {
93 Self {
94 flags: Arc::new(RwLock::new(flags)),
95 }
96 }
97
98 #[must_use]
100 pub fn get(&self, key: &str) -> bool {
101 self.flags.read().get(key)
102 }
103
104 pub fn set(&self, key: &str, value: bool) {
106 self.flags.write().set(key, value);
107 }
108
109 #[must_use]
111 pub fn snapshot(&self) -> FeatureFlags {
112 self.flags.read().clone()
113 }
114}
115
116pub const FLAG_JS_RENDERING: &str = "js_rendering";
122
123pub const FLAG_AI_ANALYZERS: &str = "ai_analyzers";
125
126pub const FLAG_WASM_ANALYZERS: &str = "wasm_analyzers";
128
129pub const FLAG_BACKLINK_ANALYSIS: &str = "backlink_analysis";
131
132pub const FLAG_RUM_INTEGRATION: &str = "rum_integration";
134
135pub const FLAG_ENCRYPTION_AT_REST: &str = "encryption_at_rest";
137
138pub const FLAG_AUDIT_TRAIL: &str = "audit_trail";
140
141pub const FLAG_OBSERVABILITY: &str = "observability";
143
144#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_feature_flags_default() {
154 let flags = FeatureFlags::default();
155 assert!(!flags.get(FLAG_JS_RENDERING));
156 assert!(flags.get(FLAG_AI_ANALYZERS));
157 assert!(flags.get(FLAG_WASM_ANALYZERS));
158 assert!(!flags.get(FLAG_BACKLINK_ANALYSIS));
159 }
160
161 #[test]
162 fn test_feature_flags_set_get() {
163 let mut flags = FeatureFlags::new();
164 flags.set("custom_flag", true);
165 assert!(flags.get("custom_flag"));
166 flags.set("custom_flag", false);
167 assert!(!flags.get("custom_flag"));
168 }
169
170 #[test]
171 fn test_feature_flags_from_toml() {
172 let toml_str = r#"
173 js_rendering = true
174 ai_analyzers = false
175 "#;
176 let flags = FeatureFlags::from_toml(toml_str).unwrap();
177 assert!(flags.get("js_rendering"));
178 assert!(!flags.get("ai_analyzers"));
179 }
180
181 #[test]
182 fn test_feature_flags_merge() {
183 let mut base = FeatureFlags::default();
184 base.set("custom", false);
185
186 let mut override_flags = FeatureFlags::new();
187 override_flags.set("custom", true);
188 override_flags.set("js_rendering", true);
189
190 let merged = base.merge(override_flags);
191 assert!(merged.get("custom"));
192 assert!(merged.get("js_rendering"));
193 }
194
195 #[test]
196 fn test_shared_feature_flags() {
197 let flags = FeatureFlags::default();
198 let shared = SharedFeatureFlags::new(flags);
199
200 assert!(!shared.get(FLAG_JS_RENDERING));
201 shared.set(FLAG_JS_RENDERING, true);
202 assert!(shared.get(FLAG_JS_RENDERING));
203 }
204}