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