riglr_config/
features.rs

1//! Feature flags configuration
2
3use crate::ConfigResult;
4use serde::{Deserialize, Serialize};
5
6/// Feature flags configuration
7#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct FeaturesConfig {
9    /// Enable trading functionality
10    #[serde(default = "default_true")]
11    pub enable_trading: bool,
12
13    /// Enable cross-chain bridging
14    #[serde(default)]
15    pub enable_bridging: bool,
16
17    /// Enable social media monitoring
18    #[serde(default)]
19    pub enable_social_monitoring: bool,
20
21    /// Enable graph-based memory
22    #[serde(default)]
23    pub enable_graph_memory: bool,
24
25    /// Enable real-time streaming
26    #[serde(default = "default_true")]
27    pub enable_streaming: bool,
28
29    /// Enable webhook notifications
30    #[serde(default)]
31    pub enable_webhooks: bool,
32
33    /// Enable analytics collection
34    #[serde(default)]
35    pub enable_analytics: bool,
36
37    /// Enable debug mode
38    #[serde(default)]
39    pub debug_mode: bool,
40
41    /// Enable experimental features
42    #[serde(default)]
43    pub experimental: bool,
44
45    /// Custom feature flags
46    #[serde(default)]
47    pub custom: std::collections::HashMap<String, bool>,
48}
49
50impl FeaturesConfig {
51    /// Check if a feature is enabled
52    pub fn is_enabled(&self, feature: Feature) -> bool {
53        match feature {
54            Feature::Trading => self.enable_trading,
55            Feature::Bridging => self.enable_bridging,
56            Feature::SocialMonitoring => self.enable_social_monitoring,
57            Feature::GraphMemory => self.enable_graph_memory,
58            Feature::Streaming => self.enable_streaming,
59            Feature::Webhooks => self.enable_webhooks,
60            Feature::Analytics => self.enable_analytics,
61            Feature::Debug => self.debug_mode,
62            Feature::Experimental => self.experimental,
63        }
64    }
65
66    /// Check if a custom feature is enabled
67    pub fn is_custom_enabled(&self, name: &str) -> bool {
68        self.custom.get(name).copied().unwrap_or(false)
69    }
70
71    /// Enable a feature
72    pub fn enable(&mut self, feature: Feature) {
73        match feature {
74            Feature::Trading => self.enable_trading = true,
75            Feature::Bridging => self.enable_bridging = true,
76            Feature::SocialMonitoring => self.enable_social_monitoring = true,
77            Feature::GraphMemory => self.enable_graph_memory = true,
78            Feature::Streaming => self.enable_streaming = true,
79            Feature::Webhooks => self.enable_webhooks = true,
80            Feature::Analytics => self.enable_analytics = true,
81            Feature::Debug => self.debug_mode = true,
82            Feature::Experimental => self.experimental = true,
83        }
84    }
85
86    /// Disable a feature
87    pub fn disable(&mut self, feature: Feature) {
88        match feature {
89            Feature::Trading => self.enable_trading = false,
90            Feature::Bridging => self.enable_bridging = false,
91            Feature::SocialMonitoring => self.enable_social_monitoring = false,
92            Feature::GraphMemory => self.enable_graph_memory = false,
93            Feature::Streaming => self.enable_streaming = false,
94            Feature::Webhooks => self.enable_webhooks = false,
95            Feature::Analytics => self.enable_analytics = false,
96            Feature::Debug => self.debug_mode = false,
97            Feature::Experimental => self.experimental = false,
98        }
99    }
100
101    /// Validate the features configuration for consistency and warnings
102    pub fn validate_config(&self) -> ConfigResult<()> {
103        // Add any feature-specific validation here
104        if self.experimental && !self.debug_mode {
105            tracing::warn!("Experimental features enabled without debug mode");
106        }
107
108        Ok(())
109    }
110}
111
112/// Feature enumeration
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum Feature {
115    /// Enable trading functionality
116    Trading,
117    /// Enable cross-chain bridging
118    Bridging,
119    /// Enable social media monitoring
120    SocialMonitoring,
121    /// Enable graph-based memory
122    GraphMemory,
123    /// Enable real-time streaming
124    Streaming,
125    /// Enable webhook notifications
126    Webhooks,
127    /// Enable analytics collection
128    Analytics,
129    /// Enable debug mode
130    Debug,
131    /// Enable experimental features
132    Experimental,
133}
134
135impl std::fmt::Display for Feature {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        match self {
138            Feature::Trading => write!(f, "trading"),
139            Feature::Bridging => write!(f, "bridging"),
140            Feature::SocialMonitoring => write!(f, "social_monitoring"),
141            Feature::GraphMemory => write!(f, "graph_memory"),
142            Feature::Streaming => write!(f, "streaming"),
143            Feature::Webhooks => write!(f, "webhooks"),
144            Feature::Analytics => write!(f, "analytics"),
145            Feature::Debug => write!(f, "debug"),
146            Feature::Experimental => write!(f, "experimental"),
147        }
148    }
149}
150
151fn default_true() -> bool {
152    true
153}
154
155impl Default for FeaturesConfig {
156    fn default() -> Self {
157        Self {
158            enable_trading: true,
159            enable_bridging: false, // Disabled by default to avoid requiring LIFI_API_KEY
160            enable_social_monitoring: false,
161            enable_graph_memory: false,
162            enable_streaming: true,
163            enable_webhooks: false,
164            enable_analytics: false,
165            debug_mode: false,
166            experimental: false,
167            custom: std::collections::HashMap::new(),
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_features_config_default() {
178        let config = FeaturesConfig::default();
179
180        // Test default values
181        assert!(config.enable_trading);
182        assert!(!config.enable_bridging); // Disabled by default to avoid requiring LIFI_API_KEY
183        assert!(!config.enable_social_monitoring);
184        assert!(!config.enable_graph_memory);
185        assert!(config.enable_streaming);
186        assert!(!config.enable_webhooks);
187        assert!(!config.enable_analytics);
188        assert!(!config.debug_mode);
189        assert!(!config.experimental);
190        assert!(config.custom.is_empty());
191    }
192
193    #[test]
194    fn test_is_enabled_when_trading_should_return_correct_value() {
195        let mut config = FeaturesConfig::default();
196        assert!(config.is_enabled(Feature::Trading));
197
198        config.enable_trading = false;
199        assert!(!config.is_enabled(Feature::Trading));
200    }
201
202    #[test]
203    fn test_is_enabled_when_bridging_should_return_correct_value() {
204        let mut config = FeaturesConfig::default();
205        assert!(!config.is_enabled(Feature::Bridging)); // Default is false
206
207        config.enable_bridging = true;
208        assert!(config.is_enabled(Feature::Bridging));
209
210        config.enable_bridging = false;
211        assert!(!config.is_enabled(Feature::Bridging));
212    }
213
214    #[test]
215    fn test_is_enabled_when_social_monitoring_should_return_correct_value() {
216        let mut config = FeaturesConfig::default();
217        assert!(!config.is_enabled(Feature::SocialMonitoring));
218
219        config.enable_social_monitoring = true;
220        assert!(config.is_enabled(Feature::SocialMonitoring));
221    }
222
223    #[test]
224    fn test_is_enabled_when_graph_memory_should_return_correct_value() {
225        let mut config = FeaturesConfig::default();
226        assert!(!config.is_enabled(Feature::GraphMemory));
227
228        config.enable_graph_memory = true;
229        assert!(config.is_enabled(Feature::GraphMemory));
230    }
231
232    #[test]
233    fn test_is_enabled_when_streaming_should_return_correct_value() {
234        let mut config = FeaturesConfig::default();
235        assert!(config.is_enabled(Feature::Streaming));
236
237        config.enable_streaming = false;
238        assert!(!config.is_enabled(Feature::Streaming));
239    }
240
241    #[test]
242    fn test_is_enabled_when_webhooks_should_return_correct_value() {
243        let mut config = FeaturesConfig::default();
244        assert!(!config.is_enabled(Feature::Webhooks));
245
246        config.enable_webhooks = true;
247        assert!(config.is_enabled(Feature::Webhooks));
248    }
249
250    #[test]
251    fn test_is_enabled_when_analytics_should_return_correct_value() {
252        let mut config = FeaturesConfig::default();
253        assert!(!config.is_enabled(Feature::Analytics));
254
255        config.enable_analytics = true;
256        assert!(config.is_enabled(Feature::Analytics));
257    }
258
259    #[test]
260    fn test_is_enabled_when_debug_should_return_correct_value() {
261        let mut config = FeaturesConfig::default();
262        assert!(!config.is_enabled(Feature::Debug));
263
264        config.debug_mode = true;
265        assert!(config.is_enabled(Feature::Debug));
266    }
267
268    #[test]
269    fn test_is_enabled_when_experimental_should_return_correct_value() {
270        let mut config = FeaturesConfig::default();
271        assert!(!config.is_enabled(Feature::Experimental));
272
273        config.experimental = true;
274        assert!(config.is_enabled(Feature::Experimental));
275    }
276
277    #[test]
278    fn test_is_custom_enabled_when_feature_exists_should_return_true() {
279        let mut config = FeaturesConfig::default();
280        config.custom.insert("custom_feature".to_string(), true);
281
282        assert!(config.is_custom_enabled("custom_feature"));
283    }
284
285    #[test]
286    fn test_is_custom_enabled_when_feature_exists_false_should_return_false() {
287        let mut config = FeaturesConfig::default();
288        config.custom.insert("custom_feature".to_string(), false);
289
290        assert!(!config.is_custom_enabled("custom_feature"));
291    }
292
293    #[test]
294    fn test_is_custom_enabled_when_feature_not_exists_should_return_false() {
295        let config = FeaturesConfig::default();
296
297        assert!(!config.is_custom_enabled("nonexistent_feature"));
298    }
299
300    #[test]
301    fn test_enable_trading_should_set_true() {
302        let mut config = FeaturesConfig::default();
303        config.enable_trading = false;
304
305        config.enable(Feature::Trading);
306        assert!(config.enable_trading);
307    }
308
309    #[test]
310    fn test_enable_bridging_should_set_true() {
311        let mut config = FeaturesConfig::default();
312        config.enable_bridging = false;
313
314        config.enable(Feature::Bridging);
315        assert!(config.enable_bridging);
316    }
317
318    #[test]
319    fn test_enable_social_monitoring_should_set_true() {
320        let mut config = FeaturesConfig::default();
321
322        config.enable(Feature::SocialMonitoring);
323        assert!(config.enable_social_monitoring);
324    }
325
326    #[test]
327    fn test_enable_graph_memory_should_set_true() {
328        let mut config = FeaturesConfig::default();
329
330        config.enable(Feature::GraphMemory);
331        assert!(config.enable_graph_memory);
332    }
333
334    #[test]
335    fn test_enable_streaming_should_set_true() {
336        let mut config = FeaturesConfig::default();
337        config.enable_streaming = false;
338
339        config.enable(Feature::Streaming);
340        assert!(config.enable_streaming);
341    }
342
343    #[test]
344    fn test_enable_webhooks_should_set_true() {
345        let mut config = FeaturesConfig::default();
346
347        config.enable(Feature::Webhooks);
348        assert!(config.enable_webhooks);
349    }
350
351    #[test]
352    fn test_enable_analytics_should_set_true() {
353        let mut config = FeaturesConfig::default();
354
355        config.enable(Feature::Analytics);
356        assert!(config.enable_analytics);
357    }
358
359    #[test]
360    fn test_enable_debug_should_set_true() {
361        let mut config = FeaturesConfig::default();
362
363        config.enable(Feature::Debug);
364        assert!(config.debug_mode);
365    }
366
367    #[test]
368    fn test_enable_experimental_should_set_true() {
369        let mut config = FeaturesConfig::default();
370
371        config.enable(Feature::Experimental);
372        assert!(config.experimental);
373    }
374
375    #[test]
376    fn test_disable_trading_should_set_false() {
377        let mut config = FeaturesConfig::default();
378
379        config.disable(Feature::Trading);
380        assert!(!config.enable_trading);
381    }
382
383    #[test]
384    fn test_disable_bridging_should_set_false() {
385        let mut config = FeaturesConfig::default();
386
387        config.disable(Feature::Bridging);
388        assert!(!config.enable_bridging);
389    }
390
391    #[test]
392    fn test_disable_social_monitoring_should_set_false() {
393        let mut config = FeaturesConfig::default();
394        config.enable_social_monitoring = true;
395
396        config.disable(Feature::SocialMonitoring);
397        assert!(!config.enable_social_monitoring);
398    }
399
400    #[test]
401    fn test_disable_graph_memory_should_set_false() {
402        let mut config = FeaturesConfig::default();
403        config.enable_graph_memory = true;
404
405        config.disable(Feature::GraphMemory);
406        assert!(!config.enable_graph_memory);
407    }
408
409    #[test]
410    fn test_disable_streaming_should_set_false() {
411        let mut config = FeaturesConfig::default();
412
413        config.disable(Feature::Streaming);
414        assert!(!config.enable_streaming);
415    }
416
417    #[test]
418    fn test_disable_webhooks_should_set_false() {
419        let mut config = FeaturesConfig::default();
420        config.enable_webhooks = true;
421
422        config.disable(Feature::Webhooks);
423        assert!(!config.enable_webhooks);
424    }
425
426    #[test]
427    fn test_disable_analytics_should_set_false() {
428        let mut config = FeaturesConfig::default();
429        config.enable_analytics = true;
430
431        config.disable(Feature::Analytics);
432        assert!(!config.enable_analytics);
433    }
434
435    #[test]
436    fn test_disable_debug_should_set_false() {
437        let mut config = FeaturesConfig::default();
438        config.debug_mode = true;
439
440        config.disable(Feature::Debug);
441        assert!(!config.debug_mode);
442    }
443
444    #[test]
445    fn test_disable_experimental_should_set_false() {
446        let mut config = FeaturesConfig::default();
447        config.experimental = true;
448
449        config.disable(Feature::Experimental);
450        assert!(!config.experimental);
451    }
452
453    #[test]
454    fn test_validate_when_experimental_without_debug_should_warn_and_return_ok() {
455        let mut config = FeaturesConfig::default();
456        config.experimental = true;
457        config.debug_mode = false;
458
459        let result = config.validate_config();
460        assert!(result.is_ok());
461    }
462
463    #[test]
464    fn test_validate_when_experimental_with_debug_should_return_ok() {
465        let mut config = FeaturesConfig::default();
466        config.experimental = true;
467        config.debug_mode = true;
468
469        let result = config.validate_config();
470        assert!(result.is_ok());
471    }
472
473    #[test]
474    fn test_validate_when_no_experimental_should_return_ok() {
475        let config = FeaturesConfig::default();
476
477        let result = config.validate_config();
478        assert!(result.is_ok());
479    }
480
481    #[test]
482    fn test_feature_display_trading() {
483        assert_eq!(Feature::Trading.to_string(), "trading");
484    }
485
486    #[test]
487    fn test_feature_display_bridging() {
488        assert_eq!(Feature::Bridging.to_string(), "bridging");
489    }
490
491    #[test]
492    fn test_feature_display_social_monitoring() {
493        assert_eq!(Feature::SocialMonitoring.to_string(), "social_monitoring");
494    }
495
496    #[test]
497    fn test_feature_display_graph_memory() {
498        assert_eq!(Feature::GraphMemory.to_string(), "graph_memory");
499    }
500
501    #[test]
502    fn test_feature_display_streaming() {
503        assert_eq!(Feature::Streaming.to_string(), "streaming");
504    }
505
506    #[test]
507    fn test_feature_display_webhooks() {
508        assert_eq!(Feature::Webhooks.to_string(), "webhooks");
509    }
510
511    #[test]
512    fn test_feature_display_analytics() {
513        assert_eq!(Feature::Analytics.to_string(), "analytics");
514    }
515
516    #[test]
517    fn test_feature_display_debug() {
518        assert_eq!(Feature::Debug.to_string(), "debug");
519    }
520
521    #[test]
522    fn test_feature_display_experimental() {
523        assert_eq!(Feature::Experimental.to_string(), "experimental");
524    }
525
526    #[test]
527    fn test_default_true_function() {
528        assert!(default_true());
529    }
530
531    #[test]
532    fn test_feature_debug_format() {
533        let feature = Feature::Trading;
534        assert_eq!(format!("{:?}", feature), "Trading");
535    }
536
537    #[test]
538    fn test_feature_clone() {
539        let feature = Feature::Trading;
540        let cloned = feature.clone();
541        assert_eq!(feature, cloned);
542    }
543
544    #[test]
545    fn test_feature_copy() {
546        let feature = Feature::Trading;
547        let copied = feature;
548        assert_eq!(feature, copied);
549    }
550
551    #[test]
552    fn test_feature_partial_eq() {
553        assert_eq!(Feature::Trading, Feature::Trading);
554        assert_ne!(Feature::Trading, Feature::Bridging);
555    }
556
557    #[test]
558    fn test_features_config_debug_format() {
559        let config = FeaturesConfig::default();
560        let debug_string = format!("{:?}", config);
561        assert!(debug_string.contains("FeaturesConfig"));
562    }
563
564    #[test]
565    fn test_features_config_clone() {
566        let config = FeaturesConfig::default();
567        let cloned = config.clone();
568        assert_eq!(config.enable_trading, cloned.enable_trading);
569        assert_eq!(config.enable_bridging, cloned.enable_bridging);
570    }
571
572    #[test]
573    fn test_serde_serialization() {
574        let config = FeaturesConfig::default();
575        let serialized = serde_json::to_string(&config);
576        assert!(serialized.is_ok());
577    }
578
579    #[test]
580    fn test_serde_deserialization() {
581        let json = r#"{"enable_trading":true,"enable_bridging":false,"enable_social_monitoring":true,"enable_graph_memory":false,"enable_streaming":true,"enable_webhooks":false,"enable_analytics":false,"debug_mode":false,"experimental":false,"custom":{}}"#;
582        let config: Result<FeaturesConfig, _> = serde_json::from_str(json);
583        assert!(config.is_ok());
584        let config = config.unwrap();
585        assert!(config.enable_trading);
586        assert!(!config.enable_bridging);
587        assert!(config.enable_social_monitoring);
588    }
589
590    #[test]
591    fn test_serde_deserialization_with_defaults() {
592        let json = r#"{}"#;
593        let config: Result<FeaturesConfig, _> = serde_json::from_str(json);
594        assert!(config.is_ok());
595        let config = config.unwrap();
596        // Should use default values
597        assert!(config.enable_trading);
598        assert!(!config.enable_bridging); // Disabled by default
599        assert!(!config.enable_social_monitoring);
600        assert!(config.enable_streaming);
601    }
602}