use std::collections::HashMap;
use std::env;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FeatureFlag {
NeuralSearch,
RemoteEmbeddings,
CrossLanguageResolution,
ExperimentalHnsw,
StreamingMcp,
GlobalAutoSync,
}
impl FeatureFlag {
pub fn env_var(&self) -> &'static str {
match self {
Self::NeuralSearch => "LEINDEX_FEATURE_NEURAL_SEARCH",
Self::RemoteEmbeddings => "LEINDEX_FEATURE_REMOTE_EMBEDDINGS",
Self::CrossLanguageResolution => "LEINDEX_FEATURE_CROSS_LANGUAGE",
Self::ExperimentalHnsw => "LEINDEX_FEATURE_EXPERIMENTAL_HNSW",
Self::StreamingMcp => "LEINDEX_FEATURE_STREAMING_MCP",
Self::GlobalAutoSync => "LEINDEX_FEATURE_GLOBAL_AUTO_SYNC",
}
}
pub fn default_value(&self) -> bool {
match self {
Self::StreamingMcp | Self::GlobalAutoSync | Self::NeuralSearch => true,
_ => false,
}
}
pub fn is_enabled(&self) -> bool {
flag_store().get(self)
}
pub fn description(&self) -> &'static str {
match self {
Self::NeuralSearch => "Enable neural (ONNX) embedding search at runtime",
Self::RemoteEmbeddings => "Enable remote embedding API (OpenAI, etc.)",
Self::CrossLanguageResolution => "Enable cross-language symbol resolution",
Self::ExperimentalHnsw => "Enable experimental HNSW algorithm parameters",
Self::StreamingMcp => "Enable streaming MCP notifications",
Self::GlobalAutoSync => "Enable global index auto-sync",
}
}
}
pub fn is_neural_enabled(config_value: bool) -> bool {
crate::feature_flags::FeatureFlag::NeuralSearch.is_enabled() && config_value
}
struct FlagStore {
values: HashMap<FeatureFlag, bool>,
}
impl FlagStore {
fn new() -> Self {
let mut values = HashMap::new();
for flag in [
FeatureFlag::NeuralSearch,
FeatureFlag::RemoteEmbeddings,
FeatureFlag::CrossLanguageResolution,
FeatureFlag::ExperimentalHnsw,
FeatureFlag::StreamingMcp,
FeatureFlag::GlobalAutoSync,
] {
let enabled = match env::var(flag.env_var()) {
Ok(v) => matches!(
v.to_lowercase().as_str(),
"1" | "true" | "yes" | "on" | "enable" | "enabled"
),
Err(_) => flag.default_value(),
};
values.insert(flag, enabled);
}
Self { values }
}
fn get(&self, flag: &FeatureFlag) -> bool {
*self.values.get(flag).unwrap_or(&false)
}
}
static FLAG_STORE: OnceLock<FlagStore> = OnceLock::new();
fn flag_store() -> &'static FlagStore {
FLAG_STORE.get_or_init(FlagStore::new)
}
pub fn all_flags() -> Vec<(FeatureFlag, bool)> {
let store = flag_store();
[
FeatureFlag::NeuralSearch,
FeatureFlag::RemoteEmbeddings,
FeatureFlag::CrossLanguageResolution,
FeatureFlag::ExperimentalHnsw,
FeatureFlag::StreamingMcp,
FeatureFlag::GlobalAutoSync,
]
.into_iter()
.map(|f| (f, store.get(&f)))
.collect()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_flag_env_var_names() {
assert_eq!(
FeatureFlag::NeuralSearch.env_var(),
"LEINDEX_FEATURE_NEURAL_SEARCH"
);
assert_eq!(
FeatureFlag::GlobalAutoSync.env_var(),
"LEINDEX_FEATURE_GLOBAL_AUTO_SYNC"
);
}
#[test]
fn test_default_values() {
assert!(FeatureFlag::NeuralSearch.default_value());
assert!(FeatureFlag::StreamingMcp.default_value());
}
}