rust_web_server/feature/mod.rs
1//! Runtime feature toggles.
2//!
3//! `FeatureStore` is a global, mutable map of named boolean flags. Handlers
4//! check flags at request time; an MCP agent (or admin API) flips them without
5//! restarting the server.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use rust_web_server::feature;
11//!
12//! // Register a flag with a default value at startup.
13//! feature::global().set("dark_launch_v2", false);
14//!
15//! // In a handler:
16//! if feature::global().is_enabled("dark_launch_v2") {
17//! // serve new code path
18//! }
19//! ```
20
21#[cfg(test)]
22mod tests;
23
24use std::collections::HashMap;
25use std::sync::{Mutex, OnceLock};
26
27/// A thread-safe map of named boolean feature flags.
28pub struct FeatureStore {
29 flags: Mutex<HashMap<String, bool>>,
30}
31
32impl FeatureStore {
33 fn new() -> Self {
34 FeatureStore { flags: Mutex::new(HashMap::new()) }
35 }
36
37 /// Set (or create) a flag. `false` disables, `true` enables.
38 pub fn set(&self, name: &str, enabled: bool) {
39 self.flags.lock().unwrap().insert(name.to_string(), enabled);
40 }
41
42 /// Returns `true` if the flag exists and is set to `true`.
43 pub fn is_enabled(&self, name: &str) -> bool {
44 *self.flags.lock().unwrap().get(name).unwrap_or(&false)
45 }
46
47 /// Snapshot of all flags sorted by name.
48 pub fn list(&self) -> Vec<(String, bool)> {
49 let mut pairs: Vec<(String, bool)> = self.flags.lock().unwrap()
50 .iter()
51 .map(|(k, v)| (k.clone(), *v))
52 .collect();
53 pairs.sort_by(|a, b| a.0.cmp(&b.0));
54 pairs
55 }
56}
57
58static INSTANCE: OnceLock<FeatureStore> = OnceLock::new();
59
60/// Return the process-wide `FeatureStore` singleton.
61pub fn global() -> &'static FeatureStore {
62 INSTANCE.get_or_init(FeatureStore::new)
63}