use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureFlags {
pub enable_vector_search: bool,
pub enable_hybrid_fusion: bool,
pub enable_graph_signals: bool,
pub enable_temporal_signals: bool,
pub enable_query_cache: bool,
pub enable_hot_reload: bool,
#[serde(default)]
pub enable_quality_weighted_graph: bool,
}
impl Default for FeatureFlags {
fn default() -> Self {
Self {
enable_vector_search: true,
enable_hybrid_fusion: true,
enable_graph_signals: true,
enable_temporal_signals: true,
enable_query_cache: true,
enable_hot_reload: true,
enable_quality_weighted_graph: false, }
}
}
impl FeatureFlags {
pub fn all_enabled() -> Self {
Self::default()
}
pub fn all_disabled() -> Self {
Self {
enable_vector_search: false,
enable_hybrid_fusion: false,
enable_graph_signals: false,
enable_temporal_signals: false,
enable_query_cache: false,
enable_hot_reload: false,
enable_quality_weighted_graph: false,
}
}
pub fn fts_only() -> Self {
Self {
enable_vector_search: false,
enable_hybrid_fusion: false,
enable_graph_signals: false,
enable_temporal_signals: false,
enable_query_cache: true,
enable_hot_reload: false,
enable_quality_weighted_graph: false,
}
}
pub fn needs_embeddings(&self) -> bool {
self.enable_vector_search
}
pub fn needs_graph_data(&self) -> bool {
self.enable_graph_signals
}
pub fn needs_temporal_data(&self) -> bool {
self.enable_temporal_signals
}
pub fn enabled_features(&self) -> Vec<&str> {
let mut features = Vec::new();
if self.enable_vector_search {
features.push("vector_search");
}
if self.enable_hybrid_fusion {
features.push("hybrid_fusion");
}
if self.enable_graph_signals {
features.push("graph_signals");
}
if self.enable_temporal_signals {
features.push("temporal_signals");
}
if self.enable_query_cache {
features.push("query_cache");
}
if self.enable_hot_reload {
features.push("hot_reload");
}
if self.enable_quality_weighted_graph {
features.push("quality_weighted_graph");
}
features
}
pub fn disabled_features(&self) -> Vec<&str> {
let mut features = Vec::new();
if !self.enable_vector_search {
features.push("vector_search");
}
if !self.enable_hybrid_fusion {
features.push("hybrid_fusion");
}
if !self.enable_graph_signals {
features.push("graph_signals");
}
if !self.enable_temporal_signals {
features.push("temporal_signals");
}
if !self.enable_query_cache {
features.push("query_cache");
}
if !self.enable_hot_reload {
features.push("hot_reload");
}
if !self.enable_quality_weighted_graph {
features.push("quality_weighted_graph");
}
features
}
pub fn enabled_count(&self) -> usize {
self.enabled_features().len()
}
pub fn disabled_count(&self) -> usize {
self.disabled_features().len()
}
pub fn is_all_enabled(&self) -> bool {
self.enabled_count() == 7
}
pub fn is_all_disabled(&self) -> bool {
self.disabled_count() == 7
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_flags() {
let flags = FeatureFlags::default();
assert!(flags.enable_vector_search);
assert!(flags.enable_hybrid_fusion);
assert!(flags.enable_graph_signals);
assert!(flags.enable_temporal_signals);
assert!(flags.enable_query_cache);
assert!(flags.enable_hot_reload);
assert!(!flags.enable_quality_weighted_graph);
assert!(!flags.is_all_enabled());
}
#[test]
fn test_all_enabled() {
let flags = FeatureFlags::all_enabled();
assert!(!flags.is_all_enabled());
assert_eq!(flags.enabled_count(), 6);
assert_eq!(flags.disabled_count(), 1);
}
#[test]
fn test_all_disabled() {
let flags = FeatureFlags::all_disabled();
assert!(flags.is_all_disabled());
assert_eq!(flags.enabled_count(), 0);
assert_eq!(flags.disabled_count(), 7);
}
#[test]
fn test_fts_only() {
let flags = FeatureFlags::fts_only();
assert!(!flags.enable_vector_search);
assert!(!flags.enable_hybrid_fusion);
assert!(!flags.enable_graph_signals);
assert!(!flags.enable_temporal_signals);
assert!(flags.enable_query_cache);
assert!(!flags.enable_hot_reload);
}
#[test]
fn test_needs_methods() {
let flags = FeatureFlags::default();
assert!(flags.needs_embeddings());
assert!(flags.needs_graph_data());
assert!(flags.needs_temporal_data());
let fts_flags = FeatureFlags::fts_only();
assert!(!fts_flags.needs_embeddings());
assert!(!fts_flags.needs_graph_data());
assert!(!fts_flags.needs_temporal_data());
}
#[test]
fn test_enabled_features() {
let flags = FeatureFlags::default();
let enabled = flags.enabled_features();
assert_eq!(enabled.len(), 6);
assert!(enabled.contains(&"vector_search"));
assert!(enabled.contains(&"hybrid_fusion"));
assert!(enabled.contains(&"graph_signals"));
assert!(enabled.contains(&"temporal_signals"));
assert!(enabled.contains(&"query_cache"));
assert!(enabled.contains(&"hot_reload"));
assert!(!enabled.contains(&"quality_weighted_graph"));
}
#[test]
fn test_disabled_features() {
let flags = FeatureFlags::all_disabled();
let disabled = flags.disabled_features();
assert_eq!(disabled.len(), 7);
assert!(disabled.contains(&"vector_search"));
assert!(disabled.contains(&"hybrid_fusion"));
assert!(disabled.contains(&"quality_weighted_graph"));
}
#[test]
fn test_partial_flags() {
let mut flags = FeatureFlags::all_disabled();
flags.enable_vector_search = true;
flags.enable_query_cache = true;
assert!(!flags.is_all_enabled());
assert!(!flags.is_all_disabled());
assert_eq!(flags.enabled_count(), 2);
assert_eq!(flags.disabled_count(), 5);
}
#[test]
fn test_quality_weighted_graph_defaults_to_false() {
let flags = FeatureFlags::default();
assert_eq!(flags.enable_quality_weighted_graph, false);
}
#[test]
fn test_quality_weighted_graph_can_be_enabled() {
let mut flags = FeatureFlags::default();
flags.enable_quality_weighted_graph = true;
assert!(flags.enable_quality_weighted_graph);
let enabled = flags.enabled_features();
assert!(enabled.contains(&"quality_weighted_graph"));
}
#[test]
fn test_backward_compat_deserialize_without_quality_flag() {
let yaml = r#"
enable_vector_search: true
enable_hybrid_fusion: true
enable_graph_signals: true
enable_temporal_signals: true
enable_query_cache: true
enable_hot_reload: true
"#;
let flags: FeatureFlags = serde_yaml::from_str(yaml).unwrap();
assert_eq!(flags.enable_quality_weighted_graph, false);
assert!(flags.enable_vector_search);
assert!(flags.enable_hybrid_fusion);
}
#[test]
fn test_deserialize_with_quality_flag_true() {
let yaml = r#"
enable_vector_search: true
enable_hybrid_fusion: true
enable_graph_signals: true
enable_temporal_signals: true
enable_query_cache: true
enable_hot_reload: true
enable_quality_weighted_graph: true
"#;
let flags: FeatureFlags = serde_yaml::from_str(yaml).unwrap();
assert!(flags.enable_quality_weighted_graph);
}
#[test]
fn test_deserialize_with_quality_flag_false() {
let yaml = r#"
enable_vector_search: true
enable_hybrid_fusion: true
enable_graph_signals: true
enable_temporal_signals: true
enable_query_cache: true
enable_hot_reload: true
enable_quality_weighted_graph: false
"#;
let flags: FeatureFlags = serde_yaml::from_str(yaml).unwrap();
assert!(!flags.enable_quality_weighted_graph);
assert!(flags.enable_vector_search);
}
}